Railway Operation Simulator  v2.19.1
A railway simulator for Windows
TrainUnit.cpp
Go to the documentation of this file.
1 // TrainUnit.cpp
2 /*
3  BEWARE OF COMMENTS in .cpp files: they were accurate when written but have
4  sometimes been overtaken by changes and not updated
5  Comments in .h files are believed to be accurate and up to date
6 
7  This is a source code file for "railway.exe", a railway operation
8  simulator, written originally in Borland C++ Builder 4 Professional with
9  later updates in Embarcadero C++Builder.
10  Copyright (C) 2010 Albert Ball [original development]
11 
12  This program is free software: you can redistribute it and/or modify
13  it under the terms of the GNU General Public License as published by
14  the Free Software Foundation, either version 3 of the License, or
15  (at your option) any later version.
16 
17  This program is distributed in the hope that it will be useful,
18  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20  GNU General Public License for more details.
21 
22  You should have received a copy of the GNU General Public License
23  along with this program. If not, see <http://www.gnu.org/licenses/>.
24 */
25 // ---------------------------------------------------------------------------
26 #include <Classes.hpp>
27 #include <Controls.hpp>
28 #include <StdCtrls.hpp>
29 #include <Forms.hpp>
30 #include <Buttons.hpp>
31 #include <ExtCtrls.hpp>
32 #include <Menus.hpp>
33 #include <Dialogs.hpp>
34 #include <Graphics.hpp>
35 #include <ComCtrls.hpp>
36 #include <fstream>
37 #include <vector>
38 #include <algorithm> //for sort
39 #include <vcl.h>
40 #include <stdlib.h> //for rand() & random()
41 #include <math.hpp> //for speed & performance calcs
42 
43 #pragma hdrstop
44 
45 #include "TrainUnit.h"
46 #include "TrackUnit.h"
47 #include "GraphicUnit.h"
48 //#include "DisplayUnit.h" included in TrackUnit.h
49 #include "PerfLogUnit.h"
50 #include "Utilities.h"
51 
52 // ---------------------------------------------------------------------------
53 #pragma package(smart_init)
54 
56 
57 // ---------------------------------------------------------------------------
58 
59 int TTrain::NextTrainID = 0; // has to be initialised outside the class
60 
61 // ---------------------------------------------------------------------------
62 
63 TExitInfo::TExitInfo() //default constructor
64 {
65  ServiceReference = " ";
66  RepeatNumber = 0;
67  TimeToExitSecs = -1;
68 }
69 
70 // ---------------------------------------------------------------------------
71 
72 TTrain::TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeedIn, int MassIn, double MaxRunningSpeedIn,
73  double MaxBrakeRateIn, double PowerAtRailIn, TTrainMode TrainModeIn, TTrainDataEntry *TrainDataEntryPtrIn, int RepeatNumberIn, int IncrementalMinutesIn,
74  int IncrementalDigitsIn, int SignallerMaxSpeedIn) : RearStartElement(RearStartElementIn), RearStartExitPos(RearStartExitPosIn), HeadCode(InputCode),
75  StartSpeed(StartSpeedIn), Mass(MassIn), MaxRunningSpeed(MaxRunningSpeedIn), MaxBrakeRate(MaxBrakeRateIn), PowerAtRail(PowerAtRailIn),
76  TrainMode(TrainModeIn), TrainDataEntryPtr(TrainDataEntryPtrIn), RepeatNumber(RepeatNumberIn), IncrementalMinutes(IncrementalMinutesIn),
77  IncrementalDigits(IncrementalDigitsIn), SignallerMaxSpeed(SignallerMaxSpeedIn)
78 /*
79  Construct a new train with general default values and input values for position and headcode.
80  Create the frontcode, headcode and background graphics here but don't delete them in a destructor.
81  This is because trains are kept in a vector and vectors erase elements during internal operations.
82  Deletion is explicit by using a special function. Increment the static class member NextTrainID
83  after setting this train's ID.
84 */
85 
86 {
87  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TTrain," + AnsiString(RearStartElementIn) + "," +
88  AnsiString(RearStartExitPosIn) + "," + AnsiString(InputCode) + "," + AnsiString(StartSpeedIn) + "," + AnsiString(MassIn) + "," +
89  AnsiString(TrainModeIn));
90  // AutoControl = true;//all trains start in auto control
91  UpdateCounter = 0;
92  TimeTimeLocArrived = false;
93  Derailed = false;
94  DerailPending = false;
95  Crashed = false;
96  StoppedAtBuffers = false;
97  StoppedAtSignal = false;
98  StoppedAtLocation = false;
99  StoppedAfterSPAD = false;
100  StoppedWithoutPower = false; // new at v2.4.0
101  StoppedForTrainInFront = false;
102  TrainInFront = false; //new at v2.18.0
103  SignallerStoppingFlag = false;
104  SignallerStopped = false;
105  SignallerRemoved = false;
106  NotInService = false;
107  HoldAtLocationInTTMode = false;
108  AllowedToPassRedSignal = false;
109  CallingOnFlag = false;
110  BeingCalledOn = false;
111  DepartureTimeSet = false;
113  TimetableFinished = false;
114  LastActionDelayFlag = false;
115  OneLengthAccelDecel = false;
116  TrainCrashedInto = -1;
118  Plotted = false;
119  TrainGone = false;
120  SPADFlag = false;
121  FrontCodePtr = new Graphics::TBitmap;
122  FrontCodePtr->PixelFormat = pf8bit;
123  FrontCodePtr->Height = 8;
124  FrontCodePtr->Width = 8;
126  FrontCodePtr->Transparent = false;
127  AValue = sqrt(2 * PowerAtRail / Mass);
129  TerminatedMessageSent = false;
130  JoinedOtherTrainFlag = false;
132  FollowOnServiceRef = ""; //added at v2.12.0
133  TreatPassAsTimeLocDeparture = false; //added at v2.12.0
134  StepForwardFlag = false;
136  for(int x = 0; x < 4; x++)
137  {
138  HeadCodeGrPtr[x] = new Graphics::TBitmap;
139  HeadCodeGrPtr[x]->PixelFormat = pf8bit;
140  HeadCodeGrPtr[x]->Height = 8;
141  HeadCodeGrPtr[x]->Width = 8;
143  HeadCodeGrPtr[x]->Transparent = false;
144  }
145  for(int x = 0; x < 4; x++)
146  {
147  BackgroundPtr[x] = new Graphics::TBitmap;
148  BackgroundPtr[x]->PixelFormat = pf8bit;
149  BackgroundPtr[x]->Height = 8;
150  BackgroundPtr[x]->Width = 8;
152  BackgroundPtr[x]->Transparent = false;
153  }
154  for(int x = 0; x < 4; x++)
155  {
157  // set here to ensure have values
158  }
159  for(int x = 0; x < 4; x++)
160  {
161  PlotElement[x] = -1; // marker for not plotted yet
162  }
163  for(int x = 0; x < 3; x++)
164  {
165  OldZoomOutElement[x] = -1; // marker for not plotted yet
166  }
168  NextTrainID++;
169 
170  // new values added to complete initialisation of all TTrain variables
171 
172  // ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)); can't be initialised yet as session trains created with Null
173  // TrainDataEntryPtr, initialise in AddTrain
175  FrontElementLength = 0;
176  EntrySpeed = 0;
177  ExitSpeedHalf = 0;
178  ExitSpeedFull = 0;
179  MaxExitSpeed = 0;
180  BrakeRate = 0;
181  CoastingBrakeRate = 0.03; //added at v2.18.0
183  FirstHalfMove = true;
184  EntryTime = 0;
185  ExitTimeHalf = 0;
186  ExitTimeFull = 0;
187  ReleaseTime = 0;
188  TRSTime = 0;
189  LastActionTime = 0;
190  Straddle = MidLag;
191  LeadElement = -1;
192  LeadEntryPos = 0;
193  LeadExitPos = 0;
194  MidElement = -1;
195  MidEntryPos = 0;
196  MidExitPos = 0;
197  LagElement = -1;
198  LagEntryPos = 0;
199  LagExitPos = 0;
200  TrainFailed = false; // added at v2.4.0
201  for(int x = 0; x < 4; x++)
202  {
203  HOffset[x] = 0;
204  VOffset[x] = 0;
205  PlotEntryPos[x] = 0;
206  }
207  OpTimeToAct = 60; // default value, new at v2.2.0
208  TimeToExit = -1;
209  ExitPair.first = -1;
210  ExitPair.second = -1;
211  MinsDelayed = 0.0; // new at v2.2.0
212  FirstLaterStopRecoverableTime = 0.0; // new at v2.2.0
213  FinishJoinLogSent = false;
214  // added at v2.4.0 to prevent repeatdly logging the event
217  // added at v2.4.0, no need to include in session file as will only be sent once & better that way
221  ZeroPowerNoCDTMessage = false;
226  ZeroPowerDepartMessage = false;
227  TrainInFrontMessage = false;
228  TrainFailurePending = false;
229  SkippedDeparture = false;
230  ActionsSkippedFlag = false;
231  SkipPtrValue = 0;
232  TrainSkippedEvents = 0;
233  DelayedRandMins = 0; //added at v2.13.0
234  NewDelay = 0; //added at v2.13.0
235  CumulativeDelayedRandMinsOneTrain = 0; //added at v2.13.0
236  ActualArrivalTime = TDateTime(0); //added at v2.13.0
237  LastSigPassedFailed = false; //added at v2.13.0
238  Utilities->CallLogPop(648);
239 }
240 
241 // ---------------------------------------------------------------------------
242 
243 void TTrain::DeleteTrain(int Caller)
244 /*
245  Delete train heap objects (bitmaps) explicitly by this special function rather than by a destructor, because vectors
246  erase elements during internal operations & if TTrain had an explicit destructor that deleted the heap elements then
247  it would be called when a vector element was erased. Calling the default TTrain destructor doesn't matter because all that
248  does is release the memory of the members (including pointers to the bitmaps), it doesn't destroy the bitmaps themselves.
249  It's important therefore to call this function before erasing the vector element, otherwise the pointers to the bitmaps
250  would be lost and the bitmaps never destroyed, thereby causing memory leaks.
251  No need to delete HeadCodePosition as that just points to existing bitmaps
252 */{
253  // if(NoDelete) return;//used when a TTrain is created to hold copied values from elsewhere
254  TrainController->LogEvent("" + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
255  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DeleteTrain," + HeadCode);
256  if(Display->ZoomOutFlag)
257  {
259  }
260  if(FrontCodePtr == 0)
261  {
262  throw Exception("Error in attempting to delete FrontCodePtr");
263  }
264  delete FrontCodePtr;
265  FrontCodePtr = 0;
266  for(int x = 0; x < 4; x++)
267  {
268  if(BackgroundPtr[x] == 0)
269  {
270  throw Exception("Error in attempting to delete BackgroundPtr[" + AnsiString(x) + "]");
271  }
272  delete BackgroundPtr[x];
273  BackgroundPtr[x] = 0;
274  }
275  for(int x = 0; x < 4; x++)
276  {
277  if(HeadCodeGrPtr[x] == 0)
278  {
279  throw Exception("Error in attempting to delete HeadCodeGrPtr[" + AnsiString(x) + "]");
280  }
281  delete HeadCodeGrPtr[x];
282  HeadCodeGrPtr[x] = 0;
283  }
284  Utilities->CallLogPop(649);
285 }
286 
287 // ---------------------------------------------------------------------------
288 
290 /*
291  Plots the train starting position on screen. Note that the check for starting on straight points &
292  on wrongly set points is carried out in TrainControllerUnit [but have to allow for starting on points because
293  ChangeDirection calls this function]. Train starts on Lead & Mid elements & Straddle = LeadMid unless
294  entering at a continuation in which case Straddle = MidLag & train not plotted immediately.
295  Set the headcode graphics pointers from the headcode text, then check whether starting at a
296  continuation. If so set Mid & Lag elements to -1 so they won't be plotted, and set Lead values
297  for the continuation element. Otherwise set Lead and Mid values,
298 
299  and Lead element value unless
300  Mid element is a buffer or continuation. Set Straddle, then for the Mid element set the graphic
301  offsets and headcode positions and front code. Pick up background bitmaps for the Mid element,
302  then check if a train on either Mid or Lag and if so give a warning message and return false so
303  that the calling function can delete the train. Plot the Mid element train values then do similarly
304  for the Lag element - set offsets, pick up background bitmaps, and plot the rear two segments of
305  the train. Finally set the Plotted flag and return true.
306 */{
307  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotStartPosition," + HeadCode);
308  int NextElementPosition, NextEntryPos, ElementLength, SpeedLimit;
309 
311  // PlotStartTime = TrainController->TTClockTime;
312  FirstHalfMove = true;
313 
314  // if enter at continuation then don't plot anything at start, but set TrainIDOnElement for continuation entry so as to
315  // 'claim' it for this train to prevent any other waiting trains trying to enter
317  {
318  LagElement = -1; // not to be plotted
319  LagExitPos = 0; // not to be plotted
320  LagEntryPos = 0; // not to be plotted
321  MidElement = -1; // not to be plotted
322  MidExitPos = 0; // not to be plotted
323  MidEntryPos = 0; // not to be plotted
325  LeadExitPos = 1; // will be 1 for continuation entry
326  LeadEntryPos = 0;
327 
329  MaxExitSpeed = StartSpeed; // initial value
331  ElementLength = Track->TrackElementAt(164, LeadElement).Length01;
332  SpeedLimit = Track->TrackElementAt(165, LeadElement).SpeedLimit01;
333  if(EntrySpeed > SpeedLimit)
334  {
335  EntrySpeed = SpeedLimit;
336  }
338  {
340  }
342  // LeadElement is the element to be entered
343 
344  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
345  // can achieve ExitSpeedFull at the half braking rate.
347  {
348  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength)); // half braking
349  if(TempEntrySpeed < EntrySpeed)
350  {
351  EntrySpeed = TempEntrySpeed;
353  }
354  }
355  Straddle = MidLag; // only for starting on a continuation
357  // no need to stop gap flashing if start on continuation
358  }
359  else // not starting at a continuation
360  {
361  LagElement = -1;
362  LagEntryPos = 0;
363  LagExitPos = 0;
370 
372  MaxExitSpeed = StartSpeed; // initial value
374  bool TempDerail = false; // dummy
375  NextElementPosition = Track->TrackElementAt(168, LeadElement).Conn[Track->GetAnyElementOppositeLinkPos(2, LeadElement, LeadEntryPos, TempDerail)];
377  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
378  {
379  StoppedWithoutPower = true;
380  }
381  // facing buffers check - ignore starting speed if start facing buffers
382  StoppedAtBuffers = false;
383  // need to set here as well as in UpdateTrain() in case paused during signaller change direction
386  {
387  FrontElementSpeedLimit = Track->TrackElementAt(494, LeadElement).SpeedLimit01; // use 01 for convenience, not used
388  FrontElementLength = Track->TrackElementAt(495, LeadElement).Length01; // use 01 for convenience, not used
389  EntrySpeed = 0;
390  ExitSpeedHalf = 0;
391  ExitSpeedFull = 0;
392  MaxExitSpeed = 0;
393  // SetTrainMovementValues not called so set this here
394  BrakeRate = 0;
397  StoppedAtSignal = false;
398  // new v2.2.0: can't be at buffers and signal! If was set then won't be reset as later
399  // signal check is an 'else'
400  if(!StoppedAtLocation)
401  {
402  StoppedAtBuffers = true; // stopped at location takes precedence
403  }
404  }
405 
406  // facing continuation check - don't allow to stop even if no power
408  {
409  FrontElementSpeedLimit = Track->TrackElementAt(509, LeadElement).SpeedLimit01; // use 01 for convenience, not used
410  FrontElementLength = Track->TrackElementAt(510, LeadElement).Length01; // use 01 for convenience, not used
414  BrakeRate = 0;
415  ExitTimeHalf = TrainController->TTClockTime + TDateTime(1.8 * (double) FrontElementLength / EntrySpeed / 86400);
416  ExitTimeFull = TrainController->TTClockTime + TDateTime(3.6 * (double) FrontElementLength / EntrySpeed / 86400);
417  }
418 
419  // Signal check
420  else if((NextElementPosition > -1) && (NextEntryPos > -1))
421  // condition check added as precaution after SloughIECC error reported by James U
422  {
423  if((Track->TrackElementAt(170, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
424  (Track->TrackElementAt(171, NextElementPosition).Attribute == 0) && !StoppedWithoutPower)
425  {
426  FrontElementSpeedLimit = Track->TrackElementAt(172, LeadElement).SpeedLimit01; // use 01 for convenience, not used
427  FrontElementLength = Track->TrackElementAt(173, LeadElement).Length01; // use 01 for convenience, not used
428  EntrySpeed = 0;
429  ExitSpeedHalf = 0;
430  ExitSpeedFull = 0;
431  MaxExitSpeed = 0;
432  BrakeRate = 0;
435  if(!StoppedAtLocation) //if it is stopped at location then don't want StoppedAtSignal until departure time if still red then, & UpdateTrain takes care of thet
436  {
437  StoppedAtSignal = true;
439  // TrainController->LogActionError(39, HeadCode, "", SignalHold, Track->TrackElementAt(754, NextElementPosition).ElementID);
440  }
442  {
443  // set both StoppedAtLocation & StoppedAtSignal, so that 'pass stop signal' is offered in popup menu rather than move
444  // forwards, but don't change the background colour so still shows as stopped at location
445  StoppedAtSignal = true;
446  }
447  }
448  else
449  {
450  StoppedAtSignal = false;
451  if(NextEntryPos > 1)
452  {
453  ElementLength = Track->TrackElementAt(174, NextElementPosition).Length23;
454  SpeedLimit = Track->TrackElementAt(175, NextElementPosition).SpeedLimit23;
455  }
456  else
457  {
458  ElementLength = Track->TrackElementAt(176, NextElementPosition).Length01;
459  SpeedLimit = Track->TrackElementAt(177, NextElementPosition).SpeedLimit01;
460  }
461  if(EntrySpeed > SpeedLimit)
462  {
463  EntrySpeed = SpeedLimit;
464  }
466  {
468  }
470  TDateTime TestTime = TrainController->TTClockTime; // test
471  AnsiString TimeString = Utilities->Format96HHMMSS(TestTime); // test
472  SetTrainMovementValues(2, NextElementPosition, NextEntryPos);
473  // NextElement is the element to be entered
474 
475  // Precautionary check - If need to brake EntrySpeed may be too high, so set it to the speed at which
476  // can achieve ExitSpeedFull at the half braking rate.
478  {
479  double TempEntrySpeed = sqrt((MaxExitSpeed * MaxExitSpeed) + (3.6 * 3.6 * MaxBrakeRate * ElementLength));
480  // half braking
481  if(TempEntrySpeed < EntrySpeed)
482  {
483  EntrySpeed = TempEntrySpeed;
484  SetTrainMovementValues(3, NextElementPosition, NextEntryPos);
485  }
486  }
487  }
488  }
490  {
491  throw Exception("Error, LeadElement Exit Connection is NotSet");
492  }
493  }
494  if(MidElement > -1) // will be -1 if start on continuation
495  {
496  Straddle = LeadMid;
500  {
501  for(int x = 0; x < 4; x++)
502  {
503  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
504  }
505  }
506  else
507  {
508  for(int x = 0; x < 4; x++)
509  {
511  }
512  }
513  if(TrainMode == Timetable)
514  {
516  }
517  else
518  {
520  }
522  // pick up background bitmaps [0] & [1] & plot HeadCodes [0] & [1]
523 
526 /* Move check to AddTrain, also, now that can start on bridges need to check that other train is on same track before refusing
527  if((Track->TrackElementAt(182, LeadElement).TrainIDOnElement > -1) || ((MidElement > -1) && (Track->TrackElementAt(183, MidElement).TrainIDOnElement > -1)))
528  {
529  ShowMessage("Can't place train " + HeadCode + "; another train already present at location");
530  Utilities->CallLogPop(651);
531  return false;
532  }
533 */
538  PlotTrainGraphic(8, 0, Display);
539  PlotTrainGraphic(9, 1, Display);
540 
543 
544  // pick up background bitmaps [2] & [3]
545 
548 
549  PlotElement[2] = MidElement;
551  PlotElement[3] = MidElement;
553  PlotTrainGraphic(10, 2, Display);
554  PlotTrainGraphic(11, 3, Display);
555  // Plotted = true; set in PlotTrainGraphic
556  }
557  Display->Update(); // resurrected when Update() dropped from PlotOutput etc
558  Utilities->CallLogPop(652);
559 }
560 
561 // ---------------------------------------------------------------------------
562 void TTrain::UnplotTrain(int Caller)
563 {
564  // Note: If trouble is experienced with the PlotAlternativeTrackRouteGraphic functions remove them & test for train on a bridge and if so call Clearand..
565  if(!Plotted)
566  {
567  return;
568  }
569  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrain," + HeadCode);
570 
571  if(Straddle == MidLag)
572  {
573  if(MidElement > -1)
574  {
579  // to force plot of locked route marker, needed once only for the element
580  }
581  if(LagElement > -1)
582  {
587  // to force plot of locked route marker, needed once only for the element
588  }
589  }
590  else if(Straddle == LeadMidLag)
591  {
592  if(LeadElement > -1)
593  {
596  // to force plot of locked route marker, needed once only for the element
597  }
598  if(MidElement > -1)
599  {
604  // to force plot of locked route marker, needed once only for the element
605  }
606  if(LagElement > -1)
607  {
610  // to force plot of locked route marker, needed once only for the element
611  }
612  }
613  else if(Straddle == LeadMid)
614  {
615  if(LeadElement > -1)
616  {
621  // to force plot of locked route marker, needed once only for the element
622  }
623  if(MidElement > -1)
624  {
629  // to force plot of locked route marker, needed once only for the element
630  }
631  }
632  if(LeadElement > -1)
633  {
635  }
636  if(MidElement > -1)
637  {
639  }
640  if(LagElement > -1)
641  {
643  }
644  Plotted = false;
646  Display->Update();
647  // without this the screen 'blinks' at next Clearand... prob forces a full repaint for some reason
648  // resurrected when Update() dropped from PlotOutput etc
649  Utilities->CallLogPop(653);
650 }
651 
652 // ----------------------------------------------------------------------------
653 
654 void TTrain::UpdateTrain(int Caller)
655 /*
656  Note: Some changes made since comments written
657 
658  Brief:
659  Enter with Straddle defining train position wrt Lag, Mid & Lead elements. Is only MidLag at this point
660  on first entry at a continuation (with no train plotted), in all other cases it is either LeadMid (when train fully
661  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
662  Thereafter on entry Straddle = LeadMidLag or LeadMid; LeadMid if train fully on Mid & Lead elements, and
663  LeadMidLag if on Lag, Mid and Lead elements (back on lag, front on Lead, & middle 2 segments on Mid).
664  If enter with Straddle = LeadMid, then train is in effect in the first half of the next element, and moves half onto it after
665  the half time point has been passed. The values for the next element were set when the train was last updated when Straddle became
666  LeadMid from LeadMidLag. After the half time point has been passed Straddle is
667  changed to MidLag within the function and all elements moved down one, old Mid becomes
668  the new lag, old Lead becomes the new Mid, and a new Lead is obtained. Then the new positions are plotted, and finally Straddle is
669  incremented to reflect the position the train now occupies.
670 
671  Detail:
672  Set TrainFailurePending if all conditions met
673  Check whether stopped at a non-red signal, and if so reset StoppedAtSignal so train can move.
674  Check whether buffers at immediate exit, either when first enter the function or later, and set StoppedAtBuffers if so
675  and return.
676  If Straddle == LeadMid then train fully on Lead and Mid, so ready for a major update:-
677  If there's a LagElement (there will be but include check for good practice - next
678  function depends on it) Check whether DerailPending set - set during last GetLeadElement if appropriate but only acted on here when
679  train fully on offending point - Derail set and DerailPanding reset, train background
680  colour changed (note that BackgroundColour is a property of the train itself) then return.
681  If no derail pending reset Lag and Mid elements to the old Mid and Lead values, reset Straddle to MidLag, then set
682  the new LeadElement, which will be the next connected element (obtain using GetLeadElement) or -1 if the current
683  LeadElement is an exit continuation. During GetLeadElement the element at LeadElement is checked and if a stop
684  signal is found StoppedAtSignal is set to true, otherwise StoppedAtSignal is set to false. Also Derail is set
685  if LeadElement is a fouled trailing point.
686  Now, the train is moved on by one segment. Firstly the last BackgroundElement is set to LagElement, then the last
687  segment of the LagElement is unplotted (if there is a LagElement - may be entering at a continuation), by
688  replotting the last background segment and checking whether the element is a bridge or crossover with the other
689  track in a route, in which case the route colour is replotted.
690  Then, if Straddle == LeadMidLag (train will move completely off the element during this function), and the train
691  track is in a route, then all the train elements are removed from the route unless it's an autosig route. Normally only the
692  LeadElement will be in a route for a moving train, but when originally placed all elements may be in the route so check them all.
693  Note also that there may be two routes at a given element position, but only one of them is the correct one, so this
694  is identified prior to the removal. Also the TrainIDs are reset because the train will be fully off this element at the end of
695  the function. If Straddle == LeadMidlag and the element being left is a ContinuationExit the the TrainGone flag is set so the
696  train can be deleted by the calling function, and the function returns.
697  If the element is a signal in the train movement direction, then it is reset to red (Attribute = 0) and is replotted
698  to show the red aspect. Finally if element is a signal in the other direction it is replotted as it was - need to
699  plot individually because could have any aspect, the background bitmap that was picked up earlier contains just the
700  basic red aspect.
701 
702  Now all the array values are updated, but the [0] values are as yet invalid, these have to be obtained explicitly from
703  the new LeadElement later. The headcode graphics are updated so that it reads correctly - left to right & top to bottom,
704  regardless of direction, and with the correct front code colour.
705 
706  The new front segment background bitmap is now picked up and the graphic offsets set, and the segments are plotted.
707  No more unplotting is needed as all but the last segment are overwritten by later segments, and the new front
708  segment is just plotted, though the background bitmap at that location has to be picked up. Just where they are
709  plotted depends on the Straddle value, [0] is always on Lead, [1] is on Lead if Straddle == LeadMidLag or Mid if
710  Straddle == MidLag; [2] is always on Mid, and [3] is on Mid if Straddle == LeadMidLag or Lag if Straddle == MidLag.
711  Also prior to plotting the lead segment a crash check is made, and if true the Crashed flag is set and the
712  TrainCrashedInto value also set to the current TrainID - this is so it too becomes crashed and hence stopped.
713 
714  The Crashed flag is now checked, and if set the front headcode colour is changed to the same as the rest of the code,
715  and the background colour changed. Then the train that is crashed into is also set to Crashed, and its colours
716  changed similarly. The function then returns.
717 
718  If Crashed is not set then Straddle is incremented and the function returns.
719 */
720 
721 {
722  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UpdateTrain," + HeadCode);
723  UpdateCounter++;
724  // 100 counts = 5secs (used in splits to prevent too frequent length checks in front & rear splits)
725  if(UpdateCounter >= 100)
726  {
727  UpdateCounter = 0;
728  }
729  int RandRange = (TrainController->MTBFHours * 3600) / 53;
730 
731  // MTBFHours is in timetable clock hours, min value is 1 & max value is 9,999 (integer values on input)
732  // but double on use because it represents timetable clock time, so at 1/16 speed RandRange is * 16 (160,000 max) & at 16x speed its /16 (1/16 min)
733  // i.e MTBFHours is Input value/TTClockSpeed (conversion is done in InterfaceUnit)
734  if(int(TrainController->RandomFailureCounter) == (rand() % 1060))
735  // RandomFailureCounter value is fixed for a full cycle of train updates so this
736  // makes sure there's no bunching of failures as there is for a fixed comparison number
737  // or a small range of comparison numbers. True every 53 secs (real time) on average rand()
738  // gives a random number between 0 and 16384 (defined as RAND_MAX in stdlib.h)
739  {
740  if(!TrainFailed && !TrainOnContinuation(0) && (RandRange > 0) && (PowerAtRail > 1) && !((TrainMode == Timetable) && TimetableFinished)
741  && !Crashed && !Derailed && !((TrainMode == Signaller) && Stopped()))
742  // RandomFailureCounter resets to 0 every 53 secs, if RandRange is 0 then no failure rate is set - i.e. failure rate = 0
743  // don't fail if:
744  // (a) on a continuation (entering or leaving);
745  // (b) already failed;
746  // (c) power effectively zero (8000) min value for powered; 0.08 for 'no power';
747  // (d) train terminated;
748  // (e) crashed or derailed; or
749  // (f) under signaller control and stopped.
750  // (g) TreatPassAsTimeLocDeparture is true //added at v2.12.0
751  {
752  if((random(RandRange) == 0) && !TreatPassAsTimeLocDeparture)
753  // max value for RandRange is over 2x10^9
754  {
755  // here if failure due
756  TrainFailurePending = true;
757  // the failure occurs when PlotElements set to proper Lead & Mid Elements
758  }
759  }
760  }
761 /* dropped as it allows a train to stop on a half element - when reach (if Stopped()) at line 1310
762  if ((PowerAtRail < 1) && (EntrySpeed < 1)) // added at v2.4.0
763  {
764  StoppedWithoutPower = true;
765  }
766 */
767 // float TimeToExit; //added at v2.10.0 Removed these so original values retained - used when train on continuation
768 // THVShortPair ExitPair; //added at v2.10.0
769  int LockedVectorNumber;
770  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
771  // default values - these needed for route checker below
772  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
773 
775  {
777  }
778  if(Crashed || Derailed)
779  {
781  {
782  PlotTrain(7, Display);
783  // replotted every cycle because of level crossing crashes, otherwise a flashing level crossing wipes out half of the train
784  Display->Update();
785  }
786  OpTimeToAct = 0.0;
787  // need to set this here as wouldn't be calculated otherwise as return from UpdateTrain
788  Utilities->CallLogPop(1017);
789  return; // no further action, user has to remove or work around
790  }
791 
793  {
795  }
797  {
799  }
800  if(StoppedWithoutPower && (TrainMode == Signaller)) //added at v2.13.2 as this condition not covered - was shown as normal
801  {
803  }
805  // introduced at v1.2.0, formerly 'TimeTimeLocArrived = false' was included
806  // in the next condition 'if(!Stopped() && !SPADFlag)' which led to repeated arrival messages if signaller control allowed a train
807  // to move & then stop again at the same station
808  {
809  TimeTimeLocArrived = false;
810  }
811  if(!Stopped() && !SPADFlag && !TrainFailed)
812  {
814  }
815  // set or release StoppedAtBuffers if fully on 2 elements depending on LeadElement
816  // Note that if LeadElement == Buffers train must be facing the buffer so no need to check orientation
817 /* old version where force a stop at buffers regardless of speed
818  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(, LeadElement).TrackType == Buffers)) StoppedAtBuffers = true;
819  else StoppedAtBuffers = false;
820 */
821 
822  // new version where crash if run into buffers
823  if(!Crashed)
824  {
825  if((Straddle == LeadMid) && (LeadElement > -1) && (Track->TrackElementAt(602, LeadElement).TrackType == Buffers))
826  {
827  if(ExitSpeedFull > 1)
828  {
829  Crashed = true;
833  // SendMissedActionLogs(3, -1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
834  // no need for missed action logs - will be sent when train removed
835  StoppedAtBuffers = false;
836  }
838  // stopped at location & stopped without power take precedence
839  {
840  StoppedAtBuffers = true;
841  }
842  else
843  {
844  StoppedAtBuffers = false;
845  }
846  }
847  else
848  {
849  StoppedAtBuffers = false;
850  }
851  }
852  else
853  {
854  StoppedAtBuffers = false;
855  }
856  // if crashed don't want stopped at buffers set
857 
858  // also crash if run into a level crossing that is changing or has barriers up
859  if(!Crashed)
860  {
861  if((Straddle == LeadMid) && (LeadElement > -1) && (ExitSpeedFull > 1))
862  {
863  int H = Track->TrackElementAt(873, LeadElement).HLoc;
864  int V = Track->TrackElementAt(874, LeadElement).VLoc;
865  if(Track->IsLCAtHV(40, H, V) && !Track->IsLCBarrierDownAtHV(2, H, V))
866  {
867  Crashed = true;
871  // no need for missed action logs - will be sent when train removed
872  }
873  }
874  }
876  {
878  }
879  // set or reset HoldAtLocationInTTMode (if true then actions are needed before train departs)
881  //if Command == "" then either TimeLoc or TimeTimeLoc so don't hold, and added last part at v2.12.0 so don't hold if have both command == pas and Treat... flag
882  {
883  HoldAtLocationInTTMode = true;
884  }
885  else if(TrainMode == Timetable)
886  {
887  HoldAtLocationInTTMode = false;
888  }
889  // in Signaller mode HoldAtLocationInTTMode not changed
890 
891  // check if departure pending & set times unless already set
892  if(TrainMode == Timetable)
893  {
895  // && !StoppedAtBuffers) - drop this, set times whether or not at buffers
896  {
897  if((ActionVectorEntryPtr->Command != "pas") && (ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) && (ActualArrivalTime > TDateTime(0)))
898  {
899  AnsiString ReasonArray[24] = {"a driver is awaited","a guard is awaited","of a medical emergency","of a technical problem","of a security issue",
900  "of a safety issue","of a disturbance","a train crew member has been taken ill","the driver has been taken ill","the guard has been taken ill",
901  "a report has been received concerning safety","a shoe has been lost under the train","of a reported theft",
902  "of an incident involving an animal","some luggage has been lost under the train","a minor repair is needed","a suspicious object has to be dealt with safely",
903  "a door is stuck open","additional stock has to be attached","a security alert","of a train fault","of an operating incident","safety checks are required",
904  "of a shortage of on train crew"};
905  //(ActionVectorEntryPtr->Command != "pas") added at v2.13.0 to rule out passes, though probably not needed
906  //(ActualArrivalTime > TDateTime(0)) added at v2.13.0 to ensure that it has been set and to dismiss trains that are present
907  //at start or have no departure time set.
908  NewDelay = 0; //section relating to random delays added at v2.13.0
909  TDateTime TimetableReleaseTime = TrainController->GetRepeatTime(0, ActionVectorEntryPtr->DepartureTime, RepeatNumber, IncrementalMinutes); //Timetable value
910  TDateTime DwellTime = TimetableReleaseTime - ActualArrivalTime; //Timetable value
911  if(DwellTime < TDateTime(30.0 / 86400))
912  {
913  DwellTime = TDateTime(30.0 / 86400);
914  }
915  int randval = random(10000);
916  if(randval != 0) //if randval == 0 or DelayMode == Nil then NewDelay will be 0 as set above
917  {
918  if(Utilities->DelayMode == Minor)
919  {
920  if(randval < Utilities->MinorDelayCutoff)
921  {
922  NewDelay = Utilities->MinorDelayFactor * log(Utilities->MinorDelayCutoff/randval);//minutes (confusingly log in C++Builder gives the natural logarithm)
923  }
924  }
925  else if(Utilities->DelayMode == Moderate)
926  {
927  if(randval < Utilities->ModerateDelayCutoff)
928  {
930  }
931  }
932  else if(Utilities->DelayMode == Major)
933  {
934  if(randval < Utilities->MajorDelayCutoff)
935  {
937  }
938  }
939  }
940 //NewDelay = 25; //test
941  if(double(TrainController->TTClockTime) <= (Utilities->LastDelayTTClockTime + 5.0/1440.0)) //if within 5 mins of last delay for any train
942  { //then don't delay. Added at v2.13.0
943  NewDelay = 0;
944  }
945  if(NewDelay < 1)
946  {
947  NewDelay = 0;
948  }
949  if(NewDelay < double(DwellTime) * 1440) //if less than scheduled dwell time then no additional delay
950  {
951  NewDelay = 0;
952  }
953  else
954  {
955  NewDelay -= double(DwellTime) * 1440;//reduce delay by dwell time
956  }
957  if(DelayedRandMins > 0)
958  {
959  DelayedRandMins -= double(DwellTime) * 1440;//reduce knock-on random delay by dwell time
960  }
961  if(DelayedRandMins < 0)
962  {
963  DelayedRandMins = 0;//can't be less than zero
964  }
966  {
967  NewDelay -= DelayedRandMins; //NewDelay is the additional delay over and above the existing knock-on delay (from earlier random delays)
968  //the formula above already includes knock-on effects
969  DelayedRandMins += NewDelay; //the new total delay, knock-on + additional
970 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //don't add here, add when depart, else this value can be > late mins
971  }
972  else
973  {
974  NewDelay = 0;
975 // CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //as above
976  }
977  ReleaseTime = LastActionTime + TDateTime(NewDelay / 1440); //earliest possible release time
978  if(NewDelay < 0.5) //less than the 30 secs min interval
979  {
980  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
981  }
982  if(ReleaseTime < TimetableReleaseTime)
983  {
984  ReleaseTime = TimetableReleaseTime; //back to correct time
985  NewDelay = 0;
986  DelayedRandMins = 0;
987  }
988  if(DelayedRandMins > double(ReleaseTime - TimetableReleaseTime) * 1440)
989  {
990  DelayedRandMins = double(ReleaseTime - TimetableReleaseTime) * 1440; //reduce this if time has been made up
991  }
992 
993  if(DelayedRandMins < NewDelay) //may be if reduced above, but if so need to reduce NewDelay also
994  {
996  }
997  //may be possible to simplify all the above but as it seems to work ok leave as is
998  if(int(NewDelay) > 0) //additional delay over and above knock-on effects from earlier random delays
999  {
1001  if(int(NewDelay) == 1)
1002  {
1003  Display->WarningLog(12, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1004  ActionVectorEntryPtr->LocationName + " by 1 minute");
1005  PerfLogForm->PerformanceLog(18, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: " + HeadCode + " delayed at " +
1006  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1007  TrainController->StopTTClockMessage(140, HeadCode + " delayed at " +
1008  ActionVectorEntryPtr->LocationName + " by 1 minute because of a minor technical issue");
1009  }
1010  else
1011  {
1012  Display->WarningLog(11, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": " + HeadCode + " delayed at " +
1013  ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) + " minutes");
1014  if(NewDelay >= 10) //give variable reasons for >= 10 mins
1015  {
1016  int randval2 = rand() % 24; //24 reasons
1017  AnsiString Reason = ReasonArray[randval2];
1019  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1020  " minutes because " + Reason);
1021  TrainController->StopTTClockMessage(141, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1022  " minutes because " + Reason);
1023  }
1024  else
1025  {
1027  HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1028  " minutes because of a minor problem");
1029  TrainController->StopTTClockMessage(142, HeadCode + " delayed at " + ActionVectorEntryPtr->LocationName + " by " + AnsiString(int(NewDelay)) +
1030  " minutes because of a minor problem");
1031  }
1032  }
1033  }
1034  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1035  ActualArrivalTime = TDateTime(0); //only run through this section once per arrival
1036  DepartureTimeSet = true;
1037  }
1038  else if(ActionVectorEntryPtr->DepartureTime > TDateTime(-1)) //as was, for trains that don't have an errival time set
1039  {//if have skipped to a new service then DepartureTime will be set (in above segement when earlier train arrived)
1040  //but ArrivalTime won't be set as it is reset to 0 at end of above segement when earlier train arrived, so this segement
1041  //will run without any random delays which might cause additional complications from mixing modifications and best avoided.
1042  NewDelay = 0;
1043  DelayedRandMins = 0;
1045  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1046  {
1047  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1048  }
1049  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1050  DepartureTimeSet = true;
1051  }
1052  else if((ActionVectorEntryPtr->Command == "pas") && TreatPassAsTimeLocDeparture) //new segment at v2.12.0 to treat a pass as a departure
1053  {//for when skip to a new service at a pass location. As above this also avoids any random delays, and will avoid above segment because
1054  //departure time isn't set - it's an event time. Again random delays in this situation might cause additional complications
1055  //from mixing modifications so best avoided.
1056  NewDelay = 0;
1057  DelayedRandMins = 0;
1059  if(ReleaseTime <= LastActionTime + TDateTime(30.0 / 86400))
1060  {
1061  ReleaseTime = LastActionTime + TDateTime(30.0 / 86400);
1062  }
1063  TRSTime = ReleaseTime - TDateTime(10.0 / 86400);
1064  DepartureTimeSet = true;
1065  }
1066  }
1067  }
1068  if(TrainController->OpTimeToActUpdateCounter == 0)// && TrainController->OpActionPanelVisible) removed last condition so always calc TimeToExit
1069  {
1070  OpTimeToAct = CalcTimeToAct(0, TimeToExit, ExitPair); // called after ReleaseTime set
1071 // this->TimeToExit = TimeToExit; don't need these as values updated directly
1072 // this->ExitPair = ExitPair;
1073  // calculate every 1 sec (in real time, not timetable time) for all trains
1074  }
1075  // check if being held at location pending any actions & deal with them if time appropriate & >= 30s since LastActionTime
1076  if(TrainMode == Timetable)
1077  {
1078  if((ActionVectorEntryPtr->Command != "Frh") && (ActionVectorEntryPtr->Command != "Frh-sh"))
1079  {
1080  RemainHereLogNotSent = true;
1081  }
1083  {
1084  // ignore TimeLoc & TTLoc departures
1085  // Action logs given in functions
1087  LastActionTime + TDateTime(30.0 / 86400)))
1088  {
1089  if(ActionVectorEntryPtr->Command == "fsp")
1090  {
1091  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1092  //'this' train. Next clock cycle will deal with any required changes
1093  FrontTrainSplit(0);
1094  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1095  { //this is checked before each return
1096  TrainHasFailed(0);
1097  }
1098  Utilities->CallLogPop(2041);
1099  return;
1100  }
1101  else if(ActionVectorEntryPtr->Command == "rsp")
1102  {
1103  // added for v1.3.2 because when add new train to TrainVector 'this' address likely invalidated, hence make no more changes to
1104  //'this' train. Next clock cycle will deal with any required changes
1105  RearTrainSplit(0);
1106  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1107  {
1108  TrainHasFailed(1);
1109  }
1110  Utilities->CallLogPop(2042);
1111  return;
1112  }
1113  else if(ActionVectorEntryPtr->Command == "Fjo")
1114  {
1115  FinishJoin(0);
1116  }
1117  else if(ActionVectorEntryPtr->Command == "jbo")
1118  {
1119  JoinedBy(0);
1120  }
1121  else if(ActionVectorEntryPtr->Command == "cdt")
1122  {
1123  ChangeTrainDirection(0, false);
1124  }
1125  else if(ActionVectorEntryPtr->Command == "dsc")
1126  {
1127  Description = ActionVectorEntryPtr->NewDescription; //changed at v2.16.1
1131  }
1132  else if(ActionVectorEntryPtr->Command == "Fns")
1133  {
1134  NewTrainService(0, false);
1135  }
1136  else if(ActionVectorEntryPtr->Command == "Frh")
1137  {
1138  RemainHere(0);
1139  }
1140  else if(ActionVectorEntryPtr->Command == "Fer")
1141  {
1142  TimetableFinished = true;
1143  }
1144  // other aspects of 'Fer' dealt with in TTrain::HasTrainGone()
1145  else if(ActionVectorEntryPtr->Command == "F-nshs")
1146  {
1148  }
1149  else if(ActionVectorEntryPtr->Command == "Frh-sh")
1150  {
1151  RepeatShuttleOrRemainHere(0, false);
1152  }
1153  else if(ActionVectorEntryPtr->Command == "Fns-sh")
1154  {
1156  }
1157 /*
1158  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
1159  shuttle headcode (no train creation)
1160  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1161  remain here
1162  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done
1163  form new service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
1164 */
1165  }
1166  }
1167  else
1168  {
1170  {
1172  }
1173  }
1174  }
1175  if(TrainMode == Timetable)
1176  {
1177  if(StoppedAtBuffers)
1178  {
1179  // error if buffers (& element before it) not at a location, or if buffer location different to ActionVectorEntryPtr location
1180  // if buffer location same as ActionVectorEntryPtr location & not Frh then error will be given for inability to depart
1181  AnsiString BufferLocation = Track->TrackElementAt(604, LeadElement).ActiveTrackElementName;
1182  if(BufferLocation == "")
1183  {
1185  }
1186  AnsiString ExpectedLocation = ActionVectorEntryPtr->LocationName;
1187  if((BufferLocation == "") || (BufferLocation != ExpectedLocation))
1188  {
1192  {
1194  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1195  // Drop missed actions so user can still use sig mode to get back on track
1197  }
1198  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1199  {
1201  TrainHasFailed(2);
1202  }
1203  Utilities->CallLogPop(1020);
1204  return;
1205  }
1206  else if((BufferLocation != "") && (BufferLocation == ExpectedLocation) && DepartureTimeSet && !RevisedStoppedAtLoc() && (TrainController->TTClockTime >
1207  ReleaseTime))
1208  {
1211  {
1214  // SendMissedActionLogs(-1, ActionVectorEntryPtr);//-1 is a marker for send messages for all remaining entries, including Fer if present
1215  // Drop missed actions so user can still use sig mode to get back on track
1217  }
1218  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1219  {
1221  TrainHasFailed(3);
1222  }
1223  Utilities->CallLogPop(1397);
1224  return;
1225  }
1226  }
1227  else
1228  {
1230  }
1231  }
1232  else
1233  {
1235  }
1236  if(TrainMode == Timetable)
1237  {
1239  {
1241  }
1243  {
1245  }
1246  }
1247  // Pick up element next to the train front (if exists) to check for calling-on, restart after a cleared signal, or
1248  // restart after stopped for train in front
1249  int NextElementPosition, NextEntryPos;
1250 
1251  if(LeadElement > -1) // if an exit continuation then not set
1252  {
1253  if((Track->TrackElementAt(186, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1254  {
1256  }
1257  else if((Track->TrackElementAt(187, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1258  {
1259  if(Track->TrackElementAt(188, LeadElement).Attribute == 0)
1260  {
1261  LeadExitPos = 1;
1262  }
1263  else
1264  {
1265  LeadExitPos = 3;
1266  }
1267  }
1268  NextElementPosition = Track->TrackElementAt(189, LeadElement).Conn[LeadExitPos];
1269  NextEntryPos = Track->TrackElementAt(190, LeadElement).ConnLinkPos[LeadExitPos];
1270  }
1271  else
1272  {
1273  NextElementPosition = -1;
1274  NextEntryPos = -1;
1275  }
1276  if((NextElementPosition > -1) && (NextEntryPos > -1))
1277  // may be buffers or continuation so need this check
1278  {
1279 /*
1280  Check whether calling-on conditions met:-
1281  a) approaching train has stopped at a signal but not at a location;
1282  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
1283  change of direction (cdt), remaining here (Frh), or under signaller control);
1284  c) at least 1 platform available for the approaching train;
1285  d) points (if any) set for direct route into platform;
1286  e) approaching train is to stop at station;
1287  f) no more facing signals between train and platform;
1288  g) [dropped g]
1289  h) train in front preventing route being set far enough to release stop signal;
1290  i) train in front not exiting at continuation;
1291  j) signal must be within 4km of the stop platform;
1292  k) [dropped (k), now can set a reoute or part route into platform so can set points more easily];
1293  l) no existing route conflicts with the route into the platform; and
1294  m) not failed or without power (these added at v2.10.0)
1295  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or
1296  other route conflicts - if a partial route set than can still change points outside the route or have a route conflict if another route is set.
1297 */
1298  if(TrainMode == Timetable)
1299  {
1300  if(CallingOnAllowed(0)) //returns false if failed or no power (modified afer v2.9.2)
1301  {
1302  CallingOnFlag = true;
1303  PlotTrainWithNewBackgroundColour(1, clCallOnBackground, Display); // calling-on background
1304  }
1305  else
1306  {
1307  if(CallingOnFlag) //TrainHasFailed sets this flag to false (at v2.10.0)
1308  {
1309  if(!TrainFailed) //shouldn't be needed but include for safety at v2.10.0
1310  {
1312  }
1313  }
1314  CallingOnFlag = false;
1315  }
1316  }
1317  if(StoppedAtSignal && ((Track->TrackElementAt(191, NextElementPosition).Attribute > 0) || AllowedToPassRedSignal) && !TrainFailed && !RevisedStoppedAtLoc())
1318  {
1319  //'&& !StoppedAtLocation' added at v2.7.0 as if had been stopped at signal before tt control restored then background colour changed to normal when signal changed from red
1320  // reset PassRedSignal when reached half-way point in next element, if reset here then SetTrainMovementValues
1321  // sets StoppedAtSignal again & train doesn't move
1322  StoppedAtSignal = false;
1323  // need to recalculate exit times since old entry time expired. Straddle now at MidLag with front of train on MidElement
1324  // hence use MidElement for the calculation so same as would have been used if signal not red, when Straddle was
1325  // LeadMidLag and front of train was on LeadElement (after the current move)
1327  EntrySpeed = 0;
1329  FirstHalfMove = true;
1330  SetTrainMovementValues(4, NextElementPosition, NextEntryPos);
1331  // NextElement is the element to be entered
1332  }
1333  if((LeadElement > -1) && (LeadExitPos > -1))//this section added at v2.18.0
1334  {
1335  int NextPos = Track->TrackElementAt(649, LeadElement).Conn[LeadExitPos];
1336  if(NextPos > -1)
1337  {
1338  int NextEntryPos = Track->TrackElementAt(1674, LeadElement).ConnLinkPos[LeadExitPos];
1339  if(Track->OtherTrainOnTrack(1, NextPos, NextEntryPos, TrainID))
1340  // true if another train on NextEntryPos track whether bridge or not
1341  {
1342  TrainInFront = true;
1343  }
1344  else
1345  {
1346  TrainInFront = false;
1347  }
1348  }
1349  }
1351  {
1352  if(ClearToNextSignal(0))
1353  {
1354  StoppedForTrainInFront = false;
1355  TrainInFront = false;
1356  BeingCalledOn = false;
1357  EntrySpeed = 0;
1359  FirstHalfMove = true;
1360  SetTrainMovementValues(16, NextElementPosition, NextEntryPos);
1361  }
1362  }
1363  }
1364  if(Stopped() && TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1365  {
1366  TrainHasFailed(4);
1367  Utilities->CallLogPop(1097);
1368  return;
1369  }
1370  if((Straddle == MidLag) && (LeadElement != -1))
1371  // later check only for Straddle == LeadMid, so need this check here for initial train start
1372  {
1374  }
1375 /* Logic below as follows: This check is made to allow a restart if had StoppedAtLocation or StoppedForTrainInFront or
1376  both but potentially able to restart (i.e. not at buffers, not crashed, not derailed, not held at location, departure
1377  time due, no train in front now & no other stop condition). Note that can be StoppedForTrainInFront when not at a
1378  location since this is set in SetTrainMovementValues whenever a train has zero EntrySpeed and there is a train in front,
1379  which could be when start as Snt.
1380  If StoppedForTrainInFront but not StoppedAtLocation then need to set TRSTime high so pink not plotted, and ReleaseTime
1381  low so can restart if appropriate. BeingCalledOn was set so that when train stopped at a station it wouldn't restart
1382  until the line was clear of trains up to the next signal. Hence check whether BeingCalledOn & if so set
1383  StoppedForTrainInFront, this ensures two things - that the restart check is carried out at each cycle and also that
1384  a restart won't happen until the line is clear to the next signal, regardless of whether or not the ReleaseTime has been
1385  reached.
1386  Then check if TRS time reached & change background to pink if so, & check if release time reached & if so change
1387  background to white and clear StoppedAtLocation. Then make check of station name, and recheck StoppedForTrainInFront,
1388  if it's set check if ClearToNextSignal and if so clear StoppedForTrainInFront & BeingCalledOn. If not ClearToNextSignal
1389  then return. If either not StoppedForTrainInFront or ClearToNextSignal then restart, calling SetTrainMovementValues &
1390  sending a message to the performancelog.
1391 */
1392 
1393  if(TrainMode == Timetable)
1394  {
1396  {
1397 // if(BeingCalledOn) //dropped when added TrainInFront at v2.18.0
1398 // {
1399 // TrainInFront = true;
1400 // }
1401  SetTrainMovementValues(25, LeadElement, LeadEntryPos); //this is purely to set StoppedForTrainInFront as needed before formal call //added at v2.19.1
1402  if((TrainController->TTClockTime >= TRSTime) && (PowerAtRail >= 1) && !StoppedForTrainInFront) //added later conditions at v2.19.1
1403  {
1405  }
1406  else //added at v2.14.0 as if a train ready to depart (pink b'gnd) taken under sig control then restored to tt control b'gnd stayed pink,
1407  { //even though release time now 30 seconds after tt control restored
1409  }
1411  {
1412  // value updated at every scheduled departure & arrival
1413  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0 moved here from after 'StoppedAtLocation = false' at v2.19.1
1414  {
1415  StoppedWithoutPower = true;
1416  }
1417  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1, don't change colour if no power or train in front
1418  {
1420  }
1421  AnsiString StationName;
1423  {
1425  }
1427  {
1429  }
1430  else
1431  {
1432  throw Exception("Error - Stopped at through station but neither lead nor mid elements have a name");
1433  }
1434  EntrySpeed = 0;
1436  if(!StoppedWithoutPower && !StoppedForTrainInFront) //added at v2.19.1 & all below put in conditional block - due to JasonB false departure log in email of 21/02/24
1437  { //= extended it to StoppedForTrainInFront as well for same reasons
1438  ZeroPowerDepartMessage = false;
1439  TrainInFrontMessage = false;
1440  FirstHalfMove = true;
1441  StoppedAtLocation = false;
1442  if((NextElementPosition > -1) && (NextEntryPos > -1))
1443  // condition check added for SloughIECC error reported by James U
1444  {
1445  if((Track->TrackElementAt(720, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1446  (Track->TrackElementAt(721, NextElementPosition).Attribute == 0))
1447  {
1448  StoppedAtSignal = true;
1450  // TrainController->LogActionError(40, HeadCode, "", SignalHold, Track->TrackElementAt(755, NextElementPosition).ElementID);
1451  }
1452  }
1454  {
1455  TimeTimeLocArrived = false;
1456  LogAction(27, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->DepartureTime, false);
1457  // no warning for TimeTimeLoc departure
1458  }
1459  else if(TreatPassAsTimeLocDeparture) //added at v2.12.0 so late/early/on time mins recorded accurately
1460  {
1461  LogAction(36, HeadCode, "", Depart, StationName, "", ActionVectorEntryPtr->EventTime, ActionVectorEntryPtr->Warning); //EventTime because the real event is a pass
1462  }
1463  else //must be TimeLoc departure
1464  {
1466  }
1467  TreatPassAsTimeLocDeparture = false; //added at v2.12.0, reset after train departs
1468  DepartureTimeSet = false;
1469  // no need to set LastActionTime for a departure
1470  //deal here with departure pointer change, increment if SkippedDeparture
1471  CumulativeDelayedRandMinsOneTrain += DelayedRandMins; //only add these after late mins added (in LogAction)
1472 
1473  if(SkippedDeparture) //only deal with this when have power
1474  {
1477  TrainSkippedEvents = 0;
1478  SkippedDeparture = false;
1479  SkipPtrValue = 0;
1480  ActionsSkippedFlag = false;
1481  }
1482  else
1483  {
1485  }
1486  // advance pointer beyond departure action - (this line (& LogAction) used to be at the end - see
1487  // note
1488 /*
1489  Note: If train stops at station after call on with a TimeTimeLoc loaded, and before the normal stop point, then when
1490  SetTrainMovementValues called it assumes a stop at the stop point because the ActionVectorEntryPtr points to a name
1491  when NameInTimetableBeforeCDT is called and the stop positions are valid. So next element train movement is based on
1492  this calculation. However, when the departure time check is made (it is during this function when SetTrainMovementValues
1493  is called), the ActionVectorEntryPtr is advanced at the end past the departure location, so at the next element when
1494  SetTrainMovementValues is called again, all is normal, i.e. the train doesn't stop again at the location. But to cure
1495  the problem move the ActionVectorEntryPtr increment to before SetTrainMovementValues.
1496 */
1498  {
1499  StoppedAtBuffers = true;
1500  }
1501  else
1502  // if buffers or no power, don't set values
1503  {
1505  {
1506  SetTrainMovementValues(12, NextElementPosition, NextEntryPos);
1507  // NextElement is the element to be entered
1508  }
1509  else
1510  {
1512  // use LeadElement for an exit continuation
1513  }
1514  }
1515  }
1516  else if(StoppedForTrainInFront) // StoppedForTrainInFront, don't advance AVPtr - added at v2.19.1
1517  {
1518  if(!TrainInFrontMessage)
1519  {
1520  TrainController->LogActionError(67, HeadCode, "", FailTrainInFront, StationName);
1521  TrainInFrontMessage = true;
1522  }
1523  }
1524  else //no power, don't advance AVPtr - added at v2.19.1
1525  {
1527  {
1529  ZeroPowerDepartMessage = true;
1530  }
1531  }
1532 
1533  }
1534  }
1535  }
1536  if(Straddle == LeadMidLag) //train on a half element
1537  {
1539  {
1540  Utilities->CallLogPop(654);
1541  return;
1542  }
1543  }
1544  else //train fully on 2 elements
1545  {
1547  {
1548  Utilities->CallLogPop(655);
1549  return;
1550  }
1551  }
1552  if((LeadElement > -1) && (MidElement > -1))
1553  {
1555  {
1556  // don't allow to stop if exiting at a continuation as causes problems if try to change direction
1557  // if entering at continuation & LeadElement is a continuation then MidElement will be -1
1558  //don't need to check for MidElement being continuation because popup menu won't show when exiting at continuation so SignallerStoppingFlag can't be set
1559  SignallerStoppingFlag = false;
1560  StepForwardFlag = false;
1561  }
1562  }
1563  if(Stopped())
1564  // this is what prevents another movement if the train is stopped
1565  {
1566  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
1567  {
1568  TrainHasFailed(5);
1569  }
1570  BrakeRate = 0;
1571  Utilities->CallLogPop(656);
1572  return;
1573  }
1574 
1575  // HERE WHEN READY FOR NEXT MOVE
1576 
1577  //added at v2.10.0 to set SPADFlag if red signal immediately ahead (as it will be if in a locked route)
1578  //check if due to run past a red signal & if so set SPADFlag (SetTrainMovementValues & its SPAD check only called when arrive fully on 2 elements)
1579  if(Straddle == LeadMid) //fully on 2 elements
1580  {
1581  if(LeadElement > -1)
1582  {
1583  if(Track->TrackElementAt(1402, LeadElement).Conn[LeadExitPos] > -1)
1584  {
1586  if(TIF.TrackType == SignalPost)
1587  {
1588  int TIFEntryPos = Track->TrackElementAt(1405, LeadElement).ConnLinkPos[LeadExitPos];
1589  int TIFExitPos = 0;
1590  if(TIFEntryPos == 0)
1591  {
1592  TIFExitPos = 1;
1593  }
1594  if((TIF.Config[TIFExitPos] == Signal) && TIF.Attribute == 0 && (ExitSpeedHalf > 1) && !AllowedToPassRedSignal && !TIF.CallingOnSet) //use ExitSpeedHalf as may have been stopped at signal so entryspeed is 0
1596  {
1597  SPADFlag = true; // user has to intervene to reset & restart after spad
1598  }
1599  }
1600  }
1601  }
1602  }
1603 
1604  // check for train in front & if so stop at next access (when train fully on element next to train)
1605  if((TrainMode == Signaller) && (Straddle == LeadMidLag))
1606  // SetTrainMovementValues brakes & stops signaller mode train for a train in front using local
1607  // variable TrainInFrontInSignallerModeFlag
1608  {
1609  if((LeadElement > -1) && (LeadExitPos > -1))
1610  {
1611  int NextPos = Track->TrackElementAt(1672, LeadElement).Conn[LeadExitPos];
1612  int NextEntryPos = Track->TrackElementAt(1675, LeadElement).ConnLinkPos[LeadExitPos];
1613  if(Track->OtherTrainOnTrack(16, NextPos, NextEntryPos, TrainID))
1614  // true if another train on NextEntryPos track whether bridge or not
1615  {
1616  TrainInFront = true;
1617  }
1618  else
1619  {
1620  TrainInFront = false;
1621  }
1622  }
1623  }
1624  if((Straddle == LeadMid) && SPADFlag)
1625  // give message + plot background when ready to move half past the signal
1626  {
1627  if(NextElementPosition > -1)
1628  {
1629  if((Track->TrackElementAt(662, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1630  (Track->TrackElementAt(663, NextElementPosition).Attribute == 0))
1631  {
1632  AnsiString LocID = AnsiString(Track->TrackElementAt(664, NextElementPosition).ElementID);
1634  // if goes past 2 signals then give message twice
1636  }
1637  }
1638  }
1639  if(Straddle == LeadMidLag)
1640  // During this function train moves fully onto 2 elements, Lead & Mid, so set next 2 moves from here for the element after Lead
1641  {
1642  // if SPADFlag set allow to keep moving until signal obscured before setting background colour, & stop only when ExitSpeedFull is 0
1643  if(SPADFlag)
1644  {
1645  if(ExitSpeedFull == 0)
1646  {
1647  StoppedAfterSPAD = true;
1648  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1649  }
1650  }
1652  {
1653  if(ExitSpeedFull == 0)
1654  {
1655  // only reach here when will stop on LeadMid, because SetTrainMovementValues called after this (i.e. ExitSpeedFull becomes 0 if not 0 now
1656  // after this test), and Straddle == LeadMidLag so not accessed at the half-move point, hence only reached at the full move
1657  // point when the speed is 0. So, colour change won't occur until fully stopped (early in UpdateTrain()), and the log message
1658  // is sent at the right time and once only.
1659  SignallerStopped = true;
1660  // but don't want to stop until have moved fully onto element, hence stop test is before this check
1661  StepForwardFlag = false;
1662  SignallerStoppingFlag = false;
1663  TTrackElement TE;
1664  AnsiString Loc = "";
1665  bool LocNamed = false;
1666  if(LeadElement > -1)
1667  {
1668  TE = Track->TrackElementAt(782, LeadElement);
1669  if(TE.ActiveTrackElementName != "")
1670  {
1671  Loc = TE.ActiveTrackElementName;
1672  LocNamed = true;
1673  }
1674  else
1675  {
1676  Loc = "track element " + TE.ElementID;
1677  }
1678  }
1679  if((MidElement > -1) && !LocNamed)
1680  {
1681  TE = Track->TrackElementAt(783, MidElement);
1682  if(TE.ActiveTrackElementName != "")
1683  {
1684  Loc = TE.ActiveTrackElementName;
1685  LocNamed = true;
1686  }
1687  else if(Loc == "")
1688  {
1689  Loc = "track element " + TE.ElementID;
1690  }
1691  }
1692  if(Loc == "")
1693  {
1694  Loc = "outside railway";
1695  // must have stopped after left at a continuation (because both lead & mid == -1)
1696  }
1697  else
1698  {
1699  Loc = "at " + Loc;
1700  }
1701  LogAction(30, HeadCode, "", SignallerStop, Loc, "", TrainController->TTClockTime, false); // false for warning
1702  }
1703  }
1704  if(LeadElement > -1) // if an exit continuation then not set
1705  {
1706  if((Track->TrackElementAt(202, LeadElement).TrackType != Points) || ((LeadEntryPos != 0) && (LeadEntryPos != 2)))
1707  {
1709  }
1710  else if((Track->TrackElementAt(203, LeadElement).TrackType == Points) && ((LeadEntryPos == 0) || (LeadEntryPos == 2)))
1711  {
1712  if(Track->TrackElementAt(204, LeadElement).Attribute == 0)
1713  {
1714  LeadExitPos = 1;
1715  }
1716  else
1717  {
1718  LeadExitPos = 3;
1719  }
1720  }
1721  NextElementPosition = Track->TrackElementAt(205, LeadElement).Conn[LeadExitPos];
1722  NextEntryPos = Track->TrackElementAt(206, LeadElement).ConnLinkPos[LeadExitPos];
1723  }
1724  else
1725  {
1726  NextElementPosition = -1;
1727  NextEntryPos = -1;
1728  }
1731  FirstHalfMove = true; //will be when finished the move onto 2 elements during this function
1732 
1733  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
1734  {
1735  StoppedWithoutPower = true;
1736  }
1737  if((NextElementPosition > -1) && (NextEntryPos > -1) && !SPADFlag)
1738  // may be buffers or continuation. SPADFlag added at v2.1.0
1739  // so don't override the SPAD colour & don't set StoppedAtSignal
1740  {
1741  if((Track->TrackElementAt(207, NextElementPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal) &&
1742  (Track->TrackElementAt(208, NextElementPosition).Attribute == 0) && (ExitSpeedFull < 1) && !RevisedStoppedAtLoc())
1743  {
1744  StoppedAtSignal = true;
1745  if(!StoppedWithoutPower)
1746  // leave background as is if no power, but set StoppedAtSignal
1747  {
1749  }
1750  // TrainController->LogActionError(41, HeadCode, "", SignalHold, Track->TrackElementAt(756, NextElementPosition).ElementID);
1751  }
1752  }
1753  if(!Stopped())
1754  {
1755  if((NextElementPosition > -1) && (NextEntryPos > -1))
1756  // may be buffers or continuation (skip SetTrainMovementValues if buffers, if
1757  // a stop element that isn't buffers - e.g. station, then will skip the calcs
1758  // during SetTrainMovementValues to avoid trying to divide by zero - see that
1759  // function for fuller explanation
1760  {
1761  SetTrainMovementValues(8, NextElementPosition, NextEntryPos);
1762  // NextElement is the element to be entered
1763  }
1764  // follow the continuation exits:-
1765  else if((LeadElement > -1) && (Track->TrackElementAt(209, LeadElement).TrackType == Continuation))
1766  {
1768  // Use LeadElement for calcs if lead is a continuation
1769  }
1770  else if((MidElement > -1) && (Track->TrackElementAt(210, MidElement).TrackType == Continuation))
1771  {
1773  // Use MidElement for calcs if mid is a continuation
1774  }
1775  else if((LagElement > -1) && (Track->TrackElementAt(211, LagElement).TrackType == Continuation))
1776  {
1778  // Use LagElement for calcs if lag is a continuation
1779  }
1780  }
1781  // remove route elements if not autosigs - this section moved from below, was under LagElement > -1 condition but needs to cover LagElement == -1
1782  if((AllRoutes->GetRouteTypeAndGraphics(2, LeadElement, LeadEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute))
1783  // Trains may not be in a route
1784  // Since Straddle = LeadMidLag at this point the train is going to move fully off the existing Lag & fully onto existing Lead element during this function
1785  {
1786  // NB if LeadElement == -1 then the above test returns NoRoute
1787  int TempH = Track->TrackElementAt(213, LeadElement).HLoc;
1788  int TempV = Track->TrackElementAt(214, LeadElement).VLoc;
1789  int TempELink = Track->TrackElementAt(215, LeadElement).Link[LeadEntryPos];
1790  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1791  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(10, TempH, TempV, SecondPair);
1792  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(143, FirstPair.first).GetFixedPrefDirElementAt(153,
1793  FirstPair.second).GetELink() == TempELink))
1794  {
1795  AllRoutes->RemoveRouteElement(10, TempH, TempV, TempELink);
1796  }
1797  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(144, SecondPair.first).GetFixedPrefDirElementAt(154,
1798  SecondPair.second).GetELink() == TempELink))
1799  {
1800  AllRoutes->RemoveRouteElement(11, TempH, TempV, TempELink);
1801  }
1802  }
1803  if(AllRoutes->GetRouteTypeAndGraphics(3, MidElement, MidEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1804  // Trains may not be in a route
1805  {
1806  int TempH = Track->TrackElementAt(216, MidElement).HLoc;
1807  int TempV = Track->TrackElementAt(217, MidElement).VLoc;
1808  int TempELink = Track->TrackElementAt(218, MidElement).Link[MidEntryPos];
1809  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1810  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(11, TempH, TempV, SecondPair);
1811  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(145, FirstPair.first).GetFixedPrefDirElementAt(155,
1812  FirstPair.second).GetELink() == TempELink))
1813  {
1814  AllRoutes->RemoveRouteElement(12, TempH, TempV, TempELink);
1815  }
1816  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(146, SecondPair.first).GetFixedPrefDirElementAt(156,
1817  SecondPair.second).GetELink() == TempELink))
1818  {
1819  AllRoutes->RemoveRouteElement(13, TempH, TempV, TempELink);
1820  }
1821  }
1822  if(AllRoutes->GetRouteTypeAndGraphics(4, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::NotAutoSigsRoute)
1823  // Trains may not be in a route
1824  {
1825  int TempH = Track->TrackElementAt(219, LagElement).HLoc;
1826  int TempV = Track->TrackElementAt(220, LagElement).VLoc;
1827  int TempELink = Track->TrackElementAt(221, LagElement).Link[LagEntryPos];
1828  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
1829  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(12, TempH, TempV, SecondPair);
1830  if((FirstPair.first > -1) && (AllRoutes->GetFixedRouteAt(147, FirstPair.first).GetFixedPrefDirElementAt(157,
1831  FirstPair.second).GetELink() == TempELink))
1832  {
1833  AllRoutes->RemoveRouteElement(14, TempH, TempV, TempELink);
1834  }
1835  else if((SecondPair.first > -1) && (AllRoutes->GetFixedRouteAt(148, SecondPair.first).GetFixedPrefDirElementAt(158,
1836  SecondPair.second).GetELink() == TempELink))
1837  {
1838  AllRoutes->RemoveRouteElement(15, TempH, TempV, TempELink);
1839  }
1840  AllRoutes->CheckMapAndRoutes(8); // test
1841  }
1842  if(LagElement > -1)
1843  // not entering at a continuation so can deal with train leaving the lag element
1844  {
1846  // amended below so route elements removed for the complete train (for NotAutoSigsRoutes), so train never standing on a route once it
1847  // starts moving, covers for eliminating route when train reaches buffers, and prevents odd route segments when route extended while
1848  // straddling 3 elements (formerly the last segment was replotted as a route & stayed plotted
1849 
1850  TPrefDirElement PrefDirElement;
1851  // plot locked route marker for any element if appropriate (i.e. if a locked AutoSigs route) but only when train leaves element completely
1852  // as this is a 16x16 graphic
1854  {
1856  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
1857  }
1858  if(ContinuationExit(2, LagElement, LagExitPos)) // true if Element is a continuation and Exitpos is the continuation end
1859  {
1860  int RouteNumber;
1861  TrainGone = true;
1862  // flag to indicate train to be deleted - outside this function
1864  {
1865  TTrainController::TContinuationAutoSigEntry ContinuationAutoSigEntry;
1866  ContinuationAutoSigEntry.RouteNumber = RouteNumber;
1867  // calc distance from & inc last signal to exit
1868  int LastElement = LagElement, LastExitPos = LagExitPos, CumDistance = 0;
1869  int NewLastElement = 0, NewLastExitPos = 0;
1870  // need above because can't change LastElement & LastExitPos until both new values obtained
1871  // while((Track->TrackElementAt(684, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200)) as was
1872  while((Track->TrackElementAt(913, LastElement).Config[LastExitPos] != Signal) && (CumDistance < 1200) && (Track->TrackElementAt(897,
1873  LastElement).TrackType != Points))
1874  // extra condition above added because of Moric1998's error (see email of 24/03/2016), where had an autosigs route across points, and another continuation on track not occupied by route so
1875  // failed when found a new element = -1 when tried to cross the continuation. Note this routine can only deal with non points as it uses GetNonPointsOppositeLinkPos
1876  // leave CumDistance as it was in these circumstances.
1877  {
1878  if(LastExitPos < 2)
1879  {
1880  CumDistance += Track->TrackElementAt(685, LastElement).Length01;
1881  }
1882  else
1883  {
1884  CumDistance += Track->TrackElementAt(686, LastElement).Length23;
1885  }
1886  NewLastElement = Track->TrackElementAt(687, LastElement).Conn[Track->GetNonPointsOppositeLinkPos(LastExitPos)];
1887  if(NewLastElement == -1)
1888  // this will catch buffers or any other connection failure
1889  {
1890  break; //throw Exception("Error, Connection = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of
1891  } //10/02/23, had two continuations linked with no signal between
1892  NewLastExitPos = Track->TrackElementAt(688, LastElement).ConnLinkPos[Track->GetNonPointsOppositeLinkPos(LastExitPos)]; //so when train exited this routine tracked
1893  if(NewLastExitPos == -1) //back to the entry continuation which had no further connection - doesn't need to be an error at all!
1894  {
1895  break; //throw Exception("Error, ConnLinkPos = -1 in Continuation loop in UpdateTrain"); //dropped at v2.15.0 because of Brent Mackie's error file of 10/02/23 , see above
1896  }
1897  LastElement = NewLastElement;
1898  LastExitPos = NewLastExitPos;
1899  }
1900  // if at signal add this in too (may not be signal if 'break;' encountered but doesn't matter)
1901  if(CumDistance < 1200)
1902  {
1903  CumDistance += Track->TrackElementAt(689, LastElement).Length01; // only need 01 for signal
1904  }
1905  // now have distance including the signal, if >=1200m use 100m (for a signal immediately after the continuation)
1906  // else use 1200m - CumDistance
1907  int FirstDistance = 0;
1908  if(CumDistance >= 1200)
1909  {
1910  FirstDistance = 100;
1911  }
1912  else
1913  {
1914  FirstDistance = 1200 - CumDistance;
1915  }
1916  if(FirstDistance < 100)
1917  {
1918  FirstDistance = 100; // don't allow < 100
1919  }
1920  // can now calc the time delays in seconds - FirstDelay, SecondDelay & ThirdDelay, these are doubles
1921  // BUT - first check whether ExitSpeedFull is very low (Mark had divide by zero error with zero exit speed using v2.4.0)
1922  if(ExitSpeedFull > 20.0)
1923  {
1924  ContinuationAutoSigEntry.FirstDelay = 3.6 * double(FirstDistance) / ExitSpeedFull;
1925  // speed in km/h & distance in m so mult by 3.6 to bring to secs
1926  ContinuationAutoSigEntry.SecondDelay = ContinuationAutoSigEntry.FirstDelay + 4320.0 / ExitSpeedFull;
1927  // 4320.0 = 3.6 * 1200, .0 to make it a double
1928  ContinuationAutoSigEntry.ThirdDelay = ContinuationAutoSigEntry.SecondDelay + 4320.0 / ExitSpeedFull;
1929  }
1930  else
1931  {
1932  ContinuationAutoSigEntry.FirstDelay = 60.0; // 60 secs between each action
1933  ContinuationAutoSigEntry.SecondDelay = 120.0;
1934  ContinuationAutoSigEntry.ThirdDelay = 180.0;
1935  }
1936  ContinuationAutoSigEntry.AccessNumber = 0;
1937  ContinuationAutoSigEntry.PassoutTime = TrainController->TTClockTime;
1939  {
1941  for(VectorIT = TrainController->ContinuationAutoSigVector.begin(); VectorIT != TrainController->ContinuationAutoSigVector.end();
1942  VectorIT++)
1943  {
1944  if(VectorIT->RouteNumber == RouteNumber)
1945  {
1946  // another train has passed out of same route so erase earlier entry
1947  TrainController->ContinuationAutoSigVector.erase(VectorIT);
1948  break;
1949  }
1950  }
1951  }
1952  TrainController->ContinuationAutoSigVector.push_back(ContinuationAutoSigEntry);
1953  }
1955  // need to plot this as returning early so will miss the later plot (not a bridge so don't need PlotAlternativeTrackRouteGraphic)
1956  Display->Update();
1957  // need to keep this since Update() not called for PlotSmallOutput as too slow
1958  Utilities->CallLogPop(659);
1959  return;
1960  }
1961  // above covers for exiting at continuation, need XLinkPos check to exclude entering at a continuation
1962  if(LeadElement > -1)
1963  {
1964  TTrackElement &TE = Track->TrackElementAt(224, LeadElement); //added at v2.13.0 for brevity
1965  if(TE.Config[LeadExitPos] == Signal)
1966  // changed to lead so reset early
1967  {
1968  LastSigPassedFailed = false; //used to cancel route elements up to next signal for autosigs route
1969  TE.Attribute = 0; // red
1970  int RouteNumber; //only used for autosigs routes
1971  //add chance to fail when train passes a signal
1972  if((random(Utilities->SignalChangeEventsPerFailure) == 0) && !TE.Failed && (Utilities->FailureMode != FNil) &&
1973  (TrainMode == Timetable) && !TE.CallingOnSet) //can't fail twice, calling on signal can't fail
1974  {
1976  IFE.TVPos = LeadElement;
1977  TE.Failed = true;
1978  Display->WarningLog(19, Utilities->Format96HHMMSS(TrainController->TTClockTime) + ": Signal failed at " + TE.ElementID);
1979  PerfLogForm->PerformanceLog(42, Utilities->Format96HHMMSS(TrainController->TTClockTime) + " WARNING: Signal failed at " + TE.ElementID);
1980  TrainController->StopTTClockMessage(129, "Signal at " + TE.ElementID +
1981  " failed when changing aspect.\nTrains can only pass under signaller control.");
1982  AllRoutes->RebuildRailwayFlag = true; //force ClearandRebuildRailway at next clock tick
1983  LastSigPassedFailed = true;
1984  //set repair time, random value in minutes between 10 and 179
1985  double FailureMinutes = double(random(Utilities->MaxRandomRepairTime) + Utilities->FixedMinRepairTime); //between 10 and 179 minutes at random
1986  TDateTime RepairTime = TrainController->TTClockTime + TDateTime(FailureMinutes / 1440);
1987  IFE.RepairTime = RepairTime;
1989  Track->FailedSignalsVector.push_back(IFE); //rearwards signals will be set when LagElement leaves signal
1990  }
1991  TE.CallingOnSet = false;
1992  // don't plot if zoomed out
1993  if(!Display->ZoomOutFlag)
1994  {
1996  }
1997  // covers signal resetting in same direction
1998  }
1999  }
2001  {
2002  AllRoutes->RebuildRailwayFlag = true; //added at v2.13.0 to replot signal after train left in case it had failed
2003  if(AllRoutes->GetRouteTypeAndGraphics(5, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2004  {
2005  Display->PlotOutput(23, Track->TrackElementAt(227, LagElement).HLoc * 16, Track->TrackElementAt(228, LagElement).VLoc * 16, EXGraphicPtr);
2006  Display->PlotOutput(24, Track->TrackElementAt(229, LagElement).HLoc * 16, Track->TrackElementAt(230, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2007  TPrefDirElement PrefDirElement;
2008  // plot locked route marker for same side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic
2010  {
2012  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2013  }
2015  LockedVectorNumber)))
2016  {
2018  }
2019  }
2020  }
2021  else if((LeadElement > -1) && (Track->TrackElementAt(233, LeadElement).TrackType == SignalPost))
2022  {
2023  Track->TrackElementAt(234, LeadElement).Attribute = 0; // red
2025  // don't plot if zoomed out
2026  if(!Display->ZoomOutFlag)
2027  {
2029  }
2030  // covers signal passed in opposite direction - replot as red, regardless of what it was before, though should already have been red
2031  }
2033  {
2034  if(AllRoutes->GetRouteTypeAndGraphics(6, LagElement, LagEntryPos, EXGraphicPtr, EntryDirectionGraphicPtr) == TAllRoutes::AutoSigsRoute)
2035  {
2036  Display->PlotOutput(26, Track->TrackElementAt(236, LagElement).HLoc * 16, Track->TrackElementAt(237, LagElement).VLoc * 16, EXGraphicPtr);
2037  Display->PlotOutput(27, Track->TrackElementAt(238, LagElement).HLoc * 16, Track->TrackElementAt(239, LagElement).VLoc * 16, EntryDirectionGraphicPtr);
2038  // below added at v1.3.0 to reset signals if back out of an autosigs route under signaller control after changing direction, when new LeadElement not on route (if it had
2039  // been the route would have been ForceCancelled). Note that the signal is not facing the direction of travel else would have entered
2040  // "if(Track->TrackElementAt(, LagElement).Config[LagExitPos] == Signal)" above and wouldn't be here
2041  int RouteNumber;
2043  // already know it's an autosigsroute, this is just to get the RouteNumber
2044  // addition below at v1.3.2 - found that a signal that had reached double yellow in ContinuationAutoSigs was reset to red when a following train's lag element
2045  // moved off a signal in the normal course of events. It was caused when a train backed out of an autosigs route under signaller control after changing
2046  // direction (see DevHistory.txt). Hence check that the train is in signaller mode and that the train's lead element isn't on the same route before calling SetRouteSignals.
2047  int RouteNumber2;
2049  // already know it's an autosigsroute, this is just to get the RouteNumber
2050  if((TrainMode == Signaller) && (RouteNumber2 != RouteNumber))
2051  // note that if not in a route (as likely) then RouteNumber2 set to -1
2052  {
2053  AllRoutes->GetFixedRouteAt(217, RouteNumber).SetRouteSignals(10);
2054  // this was in the 1.3.0 addition but without the condition
2055  }
2056  // end of 1.3.2 addition
2057  // end of 1.3.0.addition
2058  }
2059  TPrefDirElement PrefDirElement;
2060  // plot locked route marker for opp side signal if appropriate (may be covered above but leave in), but only when train leaves element completely as this is a 16x16 graphic (OK - Straddle == LeadMidLag)
2062  {
2064  RailGraphics->LockedRouteCancelPtr[PrefDirElement.GetELink()]);
2065  }
2066  }
2067  }
2068  }
2069  // straddle ONLY changed here, check if 'LeadMid' first & if so ready for updating Elements
2070  if(Straddle == LeadMid)
2071  {
2072  AllowedToPassRedSignal = false;
2073  // if had been allowed to pass then at this point it will move half onto signal so can be reset
2074  // if(LagElement > -1) ResetTrainElementID(LagElement, LagEntryPos);//train fully off old LagElement so can clear TrainOnElement flags - no, reset at earlier call when lag moves off element
2075  if(DerailPending)
2076  // set during last GetLeadElement, but only act on it when train fully on offending point
2077  // i.e. next time Straddle reaches LeadMid
2078  {
2079  Derailed = true;
2080  DerailPending = false;
2084  Utilities->CallLogPop(657);
2085  return;
2086  }
2093  Straddle = MidLag;
2094  // train now fully on the updated Lag & Mid, the front segment is going to move onto the new
2095  // LeadElement during this function (note that if stopped at signal then won't get this far)
2096  if(LeadElement > -1)
2097  {
2099  // i.e an exit continuation only
2100  // if don't exclude entry continuations then can't progress past it
2101  {
2102  LeadElement = -1;
2103  }
2104  else
2105  {
2106  GetLeadElement(0);
2107  // sets or resets DerailPending & StoppedAtSignal, and sets LeadElement values
2109  if(Stopped())
2110  {
2111  if(TrainFailurePending) // ok, stopped so PlotElements set when the train stopped in an earlier update
2112  {
2113  TrainHasFailed(6);
2114  }
2115  Utilities->CallLogPop(658);
2116  return; // i.e. don't move forward one step if next element is a red signal
2117  }
2118  }
2119  }
2120  }
2121  if(LagElement > -1)
2122  {
2123  // below are the actions required at both half moves for LagElement > -1
2125 
2126  // if was in locked route but has timed out when train leaves then plot the normal track graphic over the route graphic that is
2127  // still in BackgroundGraphic[3], if wasn't in a route then will just replot the same BackgroundGraphic
2128  // need to do this for each half element
2129 
2130  TPrefDirElement PrefDirElement;
2131  if(!(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(7, LagElement, LagExitPos, PrefDirElement, LockedVectorNumber)))
2132  {
2133  int RouteNumber; // holder for call below - not used
2135  {
2136  if(Utilities->clTransparent == TColor(0xFFFFFF))
2137  // change to black for a white background
2138  {
2140  // only applies for AutoSigs Route in case was locked & timed out
2141  }
2142  else
2143  // change to white for a dark background
2144  {
2146  // only applies for AutoSigs Route in case was locked & timed out
2147  }
2149  }
2150  }
2152  // above in case train just moving off a bridge & either alternative track in a route - need to keep its route colour,
2153  // or a train on the opposite track - needs to be replotted
2154  }
2155  // update all array values
2156  HOffset[3] = HOffset[2];
2157  HOffset[2] = HOffset[1];
2158  HOffset[1] = HOffset[0];
2159  VOffset[3] = VOffset[2];
2160  VOffset[2] = VOffset[1];
2161  VOffset[1] = VOffset[0];
2162  Graphics::TBitmap *TempPtr = BackgroundPtr[3];
2163 
2164  BackgroundPtr[3] = BackgroundPtr[2];
2165  BackgroundPtr[2] = BackgroundPtr[1];
2166  BackgroundPtr[1] = BackgroundPtr[0];
2167  BackgroundPtr[0] = TempPtr;
2168 
2169  // update headcode graphics depending on Lead entry value
2170  if(LeadElement > -1) // if Lead is -1 then stays as is
2171  {
2173  {
2174  for(int x = 0; x < 4; x++)
2175  {
2176  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
2177  }
2178  }
2179  else
2180  {
2181  for(int x = 0; x < 4; x++)
2182  {
2184  }
2185  }
2186  }
2187  if(TrainMode == Timetable)
2188  {
2190  }
2191  else
2192  {
2194  }
2196 
2197  // plot new seg [0] on Lead & [2] on Mid ([2] always on Mid)
2198  if(LeadElement > -1)
2199  {
2200  if(Straddle == MidLag)
2201  // just about to move half onto the new lead element
2202  {
2204  // pick up new background bitmap [0]
2206  int LeadElementTrainID = Track->TrackElementAt(244, LeadElement).TrainIDOnElement;
2207  if((LeadElementTrainID > -1) && (LeadElementTrainID != TrainID))
2208  // check if own ID for entry at continuation, else crashes into itself!
2209  {
2210  // OK if crossing on a bridge
2211  int OtherTrainEntryPos = TrainController->EntryPos(0, LeadElementTrainID, LeadElement);
2212  if(OtherTrainEntryPos == -1)
2213  {
2214  throw Exception("Error - OtherTrainEntryPos not set");
2215  }
2216  if((Track->TrackElementAt(246, LeadElement).TrackType != Bridge) || (LeadEntryPos == OtherTrainEntryPos) ||
2217  // LeadEntryPos for rear end crashes
2218  (LeadExitPos == OtherTrainEntryPos))
2219  // LeadExitPos for head-on crashes
2220  {
2222  Crashed = true; // only set if Straddle = MidLag
2223  CallingOnFlag = false;
2224  // in case was set, need to disable call on if call on button had been pressed
2225  }
2226  }
2227  else if(MidElement > -1) // will be -1 for continuation entries
2228  {
2229  // check if about to move onto a crossing diagonal that is occupied by another train, and if so crash
2230  int MidExitLinkNum = Track->TrackElementAt(889, MidElement).Link[MidExitPos];
2231  int MidHLoc = Track->TrackElementAt(890, MidElement).HLoc;
2232  int MidVLoc = Track->TrackElementAt(891, MidElement).VLoc;
2233  int OtherTrainID = -1;
2234  if((MidExitLinkNum == 1) || (MidExitLinkNum == 3) || (MidExitLinkNum == 7) || (MidExitLinkNum == 9))
2235  {
2236  if(Track->DiagonalFouledByTrain(0, MidHLoc, MidVLoc, MidExitLinkNum, OtherTrainID))
2237  {
2238  TrainCrashedInto = OtherTrainID;
2239  Crashed = true; // only set if Straddle = MidLag
2240  CallingOnFlag = false;
2241  // in case was set, need to disable call on if call on button had been pressed
2242  }
2243  }
2244  }
2245  }
2246  else
2247  {
2249  // pick up new background bitmap [0]
2251  }
2252  PlotElement[0] = LeadElement;
2254  PlotTrainGraphic(12, 0, Display);
2255  }
2256  if(MidElement > -1)
2257  {
2258  PlotElement[2] = MidElement;
2260  PlotTrainGraphic(1, 2, Display);
2261  }
2262  // plot the new positions for [1] & [3] graphics - [1] on Mid if Straddle = MidLag, on Lead if Straddle = LeadMidLag
2263  // [3] on Lag if Straddle = MidLag, on Mid if Straddle = LeadMidLag
2264  if(Straddle == MidLag)
2265  {
2266  if(MidElement > -1)
2267  {
2268  PlotElement[1] = MidElement;
2270  PlotTrainGraphic(2, 1, Display);
2271  }
2272  if(LagElement > -1)
2273  {
2274  PlotElement[3] = LagElement;
2276  PlotTrainGraphic(3, 3, Display);
2277  }
2278  }
2279  else // Straddle == LeadMidLag
2280  {
2281  if(LeadElement > -1)
2282  {
2283  PlotElement[1] = LeadElement;
2285  PlotTrainGraphic(4, 1, Display);
2286  }
2287  if(MidElement > -1)
2288  {
2289  PlotElement[3] = MidElement;
2291  PlotTrainGraphic(5, 3, Display);
2292  }
2293  }
2294  if(Crashed)
2295  // only reach here if crash into another train, if crash into buffers or an LC then return earlier at the if(Stopped()) test
2296  {
2301  // in case was set, need to disable call on if call on button had been pressed
2308  Straddle = LeadMidLag;
2309  // was MidLag but plotted as LeadMidLag so change Straddle accordingly
2310  Display->Update();
2311  // resurrected when Update() dropped from PlotOutput etc
2312  Utilities->CallLogPop(660);
2313  return;
2314  }
2315  // deal here with station stops & pass times after all replotting done but before Straddle changed
2316  if(TrainMode == Timetable)
2317  {
2318  if(Straddle == LeadMidLag)
2319  {
2320  if((LeadElement > -1) && (MidElement > -1) && !TimetableFinished)
2321  {
2322  // NameInTimetableBeforeCDT returns the number by which the train ActionVectorEntryPtr needs to be incremented
2323  // to point to the location arrival entry - before a change of direction
2324  AnsiString LocName = Track->TrackElementAt(249, LeadElement).ActiveTrackElementName;
2325  bool StopRequired = false;
2326  int TTVPos = NameInTimetableBeforeCDT(1, LocName, StopRequired); //excludes continuations
2327  if(TTVPos > -1) // -1 if can't find it or if name is ""
2328  {
2329  // check if at buffers (no, dropped buffer check to allow to crash into buffers) or a through station stop,
2330  // or a station where next element contains a train or a stop signal, if so
2331  // stop now, note that for 2nd check, if next element is a bridge then will have stopped by now so no need
2332  // to test the actual track the train is on since it can't be a platform
2333  TTrackElement LeadTrackElement = Track->TrackElementAt(258, LeadElement);
2334  TTrackElement NextTrackElement; // default for now
2335  bool TrainAtStopLinkPos1 = (LeadTrackElement.StationEntryStopLinkPos1 == LeadEntryPos);
2336  bool TrainAtStopLinkPos2 = (LeadTrackElement.StationEntryStopLinkPos2 == LeadEntryPos);
2337  bool TrainAtStopLinkPos3 = (LeadTrackElement.StationEntryStopLinkPos3 == LeadEntryPos);
2338  bool TrainAtStopLinkPos4 = (LeadTrackElement.StationEntryStopLinkPos4 == LeadEntryPos);
2339  bool ForwardConnection = (LeadTrackElement.Conn[LeadExitPos] > -1);
2340  int NextElementEntryPos = -1;
2341  int NextElementExitPos = -1;
2342  bool TrainOnNextElement = false;
2343  bool StopSignalAtNextElement = false;
2344  if(ForwardConnection)
2345  // if no forward connection can't derive anything from it without errors
2346  {
2347  NextTrackElement = Track->TrackElementAt(262, LeadTrackElement.Conn[LeadExitPos]);
2348  NextElementEntryPos = LeadTrackElement.ConnLinkPos[LeadExitPos];
2349  NextElementExitPos = Track->GetNonPointsOppositeLinkPos(NextElementEntryPos);
2350  // this is only for signals so no need to worry about points ambiguity
2351  TrainOnNextElement = (NextTrackElement.TrainIDOnElement > -1);
2352  StopSignalAtNextElement = ((NextTrackElement.Config[NextElementExitPos] == Signal) && (NextTrackElement.Attribute == 0));
2353  }
2354  if(TrainAtStopLinkPos1 || TrainAtStopLinkPos2 || TrainAtStopLinkPos3 || TrainAtStopLinkPos4 || (ForwardConnection && (TrainOnNextElement || StopSignalAtNextElement)))
2355  {
2356  if(TTVPos > 0)
2357  {
2359  ActionVectorEntryPtr += TTVPos;
2360  }
2361  if(StopRequired)
2362  {
2363  StoppedAtLocation = true;
2364  StoppedAtSignal = false;
2365  // may have been set earlier at line 925 so need to reset as
2366  // StoppedAtLocation takes precedence and don't want both set at same time or have flashing graphic
2367  // in zoom out mode
2368  if(!TrainFailed)
2369  {
2371  // pale green
2372  }
2374  ActualArrivalTime = TrainController->TTClockTime; //added at v2.13.0
2376  {
2377  TimeTimeLocArrived = true;
2378  // used in case of later signaller control, when need to know
2379  // whether had arrived or not, to avoid sending the arrival
2380  // message twice, see TInterface::TimetableControl1Click
2381  }
2382  }
2383  else
2384  {
2386  }
2388  {
2390  }
2391  // don't alter ActionVectorEntryPtr if at a TimeTimeLoc (& can't be anything else other than TimeLoc or PassTime after calling NameInTimetableBeforeCDT successfully)
2393  }
2394  }
2395  }
2396  }
2397  }
2398  if(Straddle == MidLag)
2399  {
2400  Straddle = LeadMidLag;
2401  FirstHalfMove = false;
2402  }
2403  else if(Straddle == LeadMidLag)
2404  {
2405  Straddle = LeadMid;
2406  FirstHalfMove = true;
2407  }
2408  else if(Straddle == LeadMid)
2409  {
2410  throw Exception("Error, Straddle shouldn't be LeadMid prior to resetting at exit from UpdateTrain");
2411  }
2412  if(TrainFailurePending) // ok, moving but PlotElements set above
2413  {
2414  TrainHasFailed(7);
2415  }
2416  Display->Update();
2417  // need to keep this since Update() not called for PlotSmallOutput as too slow
2418  Utilities->CallLogPop(661);
2419 }
2420 
2421 // ----------------------------------------------------------------------------
2422 
2423 Graphics::TBitmap *TTrain::SetOneGraphicCode(char CodeChar)
2424 {
2425  switch(CodeChar)
2426  {
2427  case '0':
2428  return(RailGraphics->Code0);
2429 
2430  case '1':
2431  return(RailGraphics->Code1);
2432 
2433  case '2':
2434  return(RailGraphics->Code2);
2435 
2436  case '3':
2437  return(RailGraphics->Code3);
2438 
2439  case '4':
2440  return(RailGraphics->Code4);
2441 
2442  case '5':
2443  return(RailGraphics->Code5);
2444 
2445  case '6':
2446  return(RailGraphics->Code6);
2447 
2448  case '7':
2449  return(RailGraphics->Code7);
2450 
2451  case '8':
2452  return(RailGraphics->Code8);
2453 
2454  case '9':
2455  return(RailGraphics->Code9);
2456 
2457  case 'A':
2458  return(RailGraphics->CodeA);
2459 
2460  case 'B':
2461  return(RailGraphics->CodeB);
2462 
2463  case 'C':
2464  return(RailGraphics->CodeC);
2465 
2466  case 'D':
2467  return(RailGraphics->CodeD);
2468 
2469  case 'E':
2470  return(RailGraphics->CodeE);
2471 
2472  case 'F':
2473  return(RailGraphics->CodeF);
2474 
2475  case 'G':
2476  return(RailGraphics->CodeG);
2477 
2478  case 'H':
2479  return(RailGraphics->CodeH);
2480 
2481  case 'I':
2482  return(RailGraphics->CodeI);
2483 
2484  case 'J':
2485  return(RailGraphics->CodeJ);
2486 
2487  case 'K':
2488  return(RailGraphics->CodeK);
2489 
2490  case 'L':
2491  return(RailGraphics->CodeL);
2492 
2493  case 'M':
2494  return(RailGraphics->CodeM);
2495 
2496  case 'N':
2497  return(RailGraphics->CodeN);
2498 
2499  case 'O':
2500  return(RailGraphics->CodeO);
2501 
2502  case 'P':
2503  return(RailGraphics->CodeP);
2504 
2505  case 'Q':
2506  return(RailGraphics->CodeQ);
2507 
2508  case 'R':
2509  return(RailGraphics->CodeR);
2510 
2511  case 'S':
2512  return(RailGraphics->CodeS);
2513 
2514  case 'T':
2515  return(RailGraphics->CodeT);
2516 
2517  case 'U':
2518  return(RailGraphics->CodeU);
2519 
2520  case 'V':
2521  return(RailGraphics->CodeV);
2522 
2523  case 'W':
2524  return(RailGraphics->CodeW);
2525 
2526  case 'X':
2527  return(RailGraphics->CodeX);
2528 
2529  case 'Y':
2530  return(RailGraphics->CodeY);
2531 
2532  case 'Z':
2533  return(RailGraphics->CodeZ);
2534 
2535  case 'a':
2536  return(RailGraphics->Code_a);
2537 
2538  case 'b':
2539  return(RailGraphics->Code_b);
2540 
2541  case 'c':
2542  return(RailGraphics->Code_c);
2543 
2544  case 'd':
2545  return(RailGraphics->Code_d);
2546 
2547  case 'e':
2548  return(RailGraphics->Code_e);
2549 
2550  case 'f':
2551  return(RailGraphics->Code_f);
2552 
2553  case 'g':
2554  return(RailGraphics->Code_g);
2555 
2556  case 'h':
2557  return(RailGraphics->Code_h);
2558 
2559  case 'i':
2560  return(RailGraphics->Code_i);
2561 
2562  case 'j':
2563  return(RailGraphics->Code_j);
2564 
2565  case 'k':
2566  return(RailGraphics->Code_k);
2567 
2568  case 'l':
2569  return(RailGraphics->Code_l);
2570 
2571  case 'm':
2572  return(RailGraphics->Code_m);
2573 
2574  case 'n':
2575  return(RailGraphics->Code_n);
2576 
2577  case 'o':
2578  return(RailGraphics->Code_o);
2579 
2580  case 'p':
2581  return(RailGraphics->Code_p);
2582 
2583  case 'q':
2584  return(RailGraphics->Code_q);
2585 
2586  case 'r':
2587  return(RailGraphics->Code_r);
2588 
2589  case 's':
2590  return(RailGraphics->Code_s);
2591 
2592  case 't':
2593  return(RailGraphics->Code_t);
2594 
2595  case 'u':
2596  return(RailGraphics->Code_u);
2597 
2598  case 'v':
2599  return(RailGraphics->Code_v);
2600 
2601  case 'w':
2602  return(RailGraphics->Code_w);
2603 
2604  case 'x':
2605  return(RailGraphics->Code_x);
2606 
2607  case 'y':
2608  return(RailGraphics->Code_y);
2609 
2610  case 'z':
2611  return(RailGraphics->Code_z);
2612 
2613  default:
2614  return(RailGraphics->TempHeadCode);
2615  }
2616 }
2617 
2618 // ----------------------------------------------------------------------------
2619 
2620 void TTrain::SetHeadCodeGraphics(int Caller, AnsiString Code)
2621 {
2622  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetHeadCodeGraphics," + HeadCode);
2623  if(Code.Length() != 4)
2624  {
2625  TrainController->StopTTClockMessage(62, "Headcode Incorrect length");
2626  }
2627  for(int x = 1; x < 5; x++) // AnsiString indices start at 1
2628  {
2629  HeadCodeGrPtr[x - 1]->Assign(SetOneGraphicCode(Code[x]));
2630  }
2631  if(BackgroundColour != clB5G5R5)
2632  // i.e. not the basic graphic colour as loaded from resource file
2633  {
2634  for(int x = 0; x < 4; x++)
2635  {
2637  }
2638  }
2639  Utilities->CallLogPop(1484);
2640 }
2641 
2642 // ----------------------------------------------------------------------------
2643 
2644 void TTrain::GetLeadElement(int Caller)
2645 // assumes Mid & Lag already set, sets LeadElement,
2646 // LeadEntryPos, LeadExitPos & DerailPending (don't want to act on it immediately)
2647 {
2648  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetLeadElement," + HeadCode);
2649  DerailPending = false;
2653  {
2654  // attr 0=straight, - links 0 & 1 (0 = lead)
2655  // attr 1=diverging, - links 2 & 3 (2 = lead)
2656  // set appropriate next element or derail - use a subroutine & return element & bool for derail
2657  // points always have links 0 & 2 = lead, link 1 = trailing straight, link 3 = training diverging
2658 
2659  // if enter at lead, exit at whatever attr set at
2660  // if enter at lag, exit at lead, but set derail wrt attribute
2661  if((LeadEntryPos == 0) && (Track->TrackElementAt(272, LeadElement).Attribute == 0))
2662  {
2663  LeadExitPos = 1;
2664  }
2665 
2666  // strictly speaking shouldn't need to set to 0 and 2 correctly since TrackIsInARoute caters for both, but
2667  // best to be on safe side
2668  else if(LeadEntryPos == 0)
2669  {
2670  LeadEntryPos = 2;
2671  LeadExitPos = 3;
2672  }
2673  else if((LeadEntryPos == 2) && (Track->TrackElementAt(273, LeadElement).Attribute == 0))
2674  {
2675  LeadEntryPos = 0;
2676  LeadExitPos = 1;
2677  }
2678  else if(LeadEntryPos == 2)
2679  {
2680  LeadExitPos = 3;
2681  }
2682  else if((LeadEntryPos == 1) && (Track->TrackElementAt(274, LeadElement).Attribute == 0))
2683  {
2684  LeadExitPos = 0;
2685  }
2686  else if(LeadEntryPos == 1)
2687  {
2688  LeadExitPos = 0;
2689  DerailPending = true;
2690  }
2691  else if((LeadEntryPos == 3) && (Track->TrackElementAt(275, LeadElement).Attribute == 0))
2692  {
2693  LeadExitPos = 0;
2694  DerailPending = true;
2695  }
2696  else if(LeadEntryPos == 3)
2697  {
2698  LeadExitPos = 0;
2699  }
2700  }
2701  else if(LeadEntryPos == 0)
2702  {
2703  LeadExitPos = 1;
2704  }
2705  else if(LeadEntryPos == 1)
2706  {
2707  LeadExitPos = 0;
2708  }
2709  else if(LeadEntryPos == 2)
2710  {
2711  LeadExitPos = 3;
2712  }
2713  else if(LeadEntryPos == 3)
2714  {
2715  LeadExitPos = 2;
2716  }
2717  // TTrackElement TrackElement = Track->TrackElementAt(276, LeadElement);
2718 /* signal check moved to Update() function
2719  if((TrackElement.TrackType == SignalPost) && (TrackElement.Config[LeadExitPos] == Signal)
2720  && (TrackElement.Attribute == 0))//0 = red
2721  {
2722  StoppedAtSignal = true; //comment out for test of locked route graphic replot
2723  }
2724  else
2725  {
2726  StoppedAtSignal = false;
2727  }
2728 */
2729  Utilities->CallLogPop(662);
2730 }
2731 
2732 // ----------------------------------------------------------------------------
2733 
2734 void TTrain::GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
2735 {
2736  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetOffsetValues," + AnsiString(Link) + "," + HeadCode);
2737  switch(Link)
2738  {
2739  case 1:
2740  {
2741  HOffset = 0;
2742  VOffset = 0;
2743  break;
2744  }
2745 
2746  case 2:
2747  {
2748  HOffset = 4;
2749  VOffset = 0;
2750  break;
2751  }
2752 
2753  case 3:
2754  {
2755  HOffset = 8;
2756  VOffset = 0;
2757  break;
2758  }
2759 
2760  case 4:
2761  {
2762  HOffset = 0;
2763  VOffset = 4;
2764  break;
2765  }
2766 
2767  case 6:
2768  {
2769  HOffset = 8;
2770  VOffset = 4;
2771  break;
2772  }
2773 
2774  case 7:
2775  {
2776  HOffset = 0;
2777  VOffset = 8;
2778  break;
2779  }
2780 
2781  case 8:
2782  {
2783  HOffset = 4;
2784  VOffset = 8;
2785  break;
2786  }
2787 
2788  case 9:
2789  {
2790  HOffset = 8;
2791  VOffset = 8;
2792  break;
2793  }
2794 
2795  default:
2796  {
2797  throw Exception("Error in GetOffsetValues - Link value wrong");
2798  }
2799  }
2800  Utilities->CallLogPop(674);
2801 }
2802 
2803 // ---------------------------------------------------------------------------
2804 
2805 bool TTrain::LowEntryValue(int EntryLink) const
2806 {
2807 /* returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i.e.
2808  the character that is red or blue) is the last character of the headcode, otherwise it's the first character of the headcode
2809 */
2810  if((EntryLink == 1) || (EntryLink == 2) || (EntryLink == 4) || (EntryLink == 7))
2811  {
2812  return(true);
2813  }
2814  else
2815  {
2816  return(false);
2817  }
2818 }
2819 
2820 // ---------------------------------------------------------------------------
2821 
2822 void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
2823 {
2824  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) + "," +
2825  AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
2826  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
2827  // default values
2828  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
2829 
2830  TAllRoutes::TRouteType RouteType;
2831 
2832  RouteType = AllRoutes->GetRouteTypeAndGraphics(11, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
2833 
2834  TRect SourceRect, DestRect;
2835 
2836  DestRect.init(0, 0, 8, 8); // initialise left, top, right, bottom
2837  // note right and bottom rect co-ordinates are 1 greater than the pixel area
2838  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
2839  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap;
2840 
2841  TempGraphic->PixelFormat = pf8bit;
2842  TempGraphic->Width = 16;
2843  TempGraphic->Height = 16;
2844  TTrackElement TempElement = Track->TrackElementAt(286, Element);
2845 
2846  if(TempElement.TrackType == Points)
2847  {
2848  TempGraphic->Assign(TempElement.GraphicPtr);
2849  TempGraphic->Transparent = true;
2850  TempGraphic->TransparentColor = Utilities->clTransparent;
2851  if(RouteType == TAllRoutes::AutoSigsRoute)
2852  {
2853  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2854  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2855  }
2856  else
2857  {
2858  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
2859  }
2860  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2861  }
2862  else if(TempElement.TrackType == GapJump) // plot set gap
2863  {
2864  if(TempElement.SpeedTag == 88)
2865  {
2866  TempGraphic->Assign(RailGraphics->gl88set);
2867  }
2868  else if(TempElement.SpeedTag == 89)
2869  {
2870  TempGraphic->Assign(RailGraphics->gl89set);
2871  }
2872  else if(TempElement.SpeedTag == 90)
2873  {
2874  TempGraphic->Assign(RailGraphics->gl90set);
2875  }
2876  else if(TempElement.SpeedTag == 91)
2877  {
2878  TempGraphic->Assign(RailGraphics->gl91set);
2879  }
2880  else if(TempElement.SpeedTag == 92)
2881  {
2882  TempGraphic->Assign(RailGraphics->gl92set);
2883  }
2884  else if(TempElement.SpeedTag == 93)
2885  {
2886  TempGraphic->Assign(RailGraphics->bm93set);
2887  }
2888  else if(TempElement.SpeedTag == 94)
2889  {
2890  TempGraphic->Assign(RailGraphics->bm94set);
2891  }
2892  else if(TempElement.SpeedTag == 95)
2893  {
2894  TempGraphic->Assign(RailGraphics->gl95set);
2895  }
2896  TempGraphic->Transparent = true;
2897  TempGraphic->TransparentColor = Utilities->clTransparent;
2898  if(RouteType == TAllRoutes::AutoSigsRoute)
2899  {
2900  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2901  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2902  }
2903  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2904  }
2905  // new for version 0.6
2906  else if(TempElement.TrackType == SignalPost)
2907  {
2908  if(TempElement.SigAspect == TTrackElement::GroundSignal)
2909  {
2910  for(int x = 0; x < 40; x++)
2911  {
2912  if((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
2913  // need to stop aspect
2914  {
2915  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
2916  break;
2917  }
2918  }
2919  }
2920  else // normal signal
2921  {
2922  TempGraphic->Assign(TempElement.GraphicPtr);
2923  // GraphicPtr set to normal signal in a signal track element
2924  }
2925  TempGraphic->Transparent = true;
2926  TempGraphic->TransparentColor = Utilities->clTransparent;
2927  if(RouteType == TAllRoutes::AutoSigsRoute)
2928  {
2929  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2930  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2931  }
2932  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2933  }
2934  else
2935  {
2936  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
2937  // can't name points gaps or signals so 'else' OK
2938  bool FoundFlag;
2939  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
2940  if(FoundFlag)
2941  {
2943  {
2944  GraphicPtr->Canvas->CopyRect(DestRect, Track->InactiveTrackElementAt(26, IMPair.first).GraphicPtr->Canvas, SourceRect);
2945  TempGraphic->Assign(RailGraphics->bmName);
2946  TempGraphic->Transparent = true;
2947  TempGraphic->TransparentColor = Utilities->clTransparent;
2948  if(RouteType == TAllRoutes::AutoSigsRoute)
2949  {
2950  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2951  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2952  }
2953  else
2954  {
2955  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
2956  }
2957  // draw track on top
2958  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2959  }
2960  else if(Track->InactiveTrackElementAt(116, IMPair.first).TrackType == LevelCrossing)
2961  {
2962  TempGraphic->Assign(TempElement.GraphicPtr);
2963  TempGraphic->Transparent = true;
2964  TempGraphic->TransparentColor = Utilities->clTransparent;
2965  // note that can't be an AutoSigsRoute
2966  // now overlay the LC central portion
2967  int BDVectorPos = -1; //not used
2968  if(Track->AnyLinkedBarrierDownVectorManual(0, Track->InactiveTrackElementAt(130, IMPair.first).HLoc, Track->InactiveTrackElementAt(131, IMPair.first).VLoc, BDVectorPos))
2969  {
2970  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlainMan);
2971  }
2972  else
2973  {
2974  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
2975  }
2976  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2977  }
2978  else
2979  {
2980  TempGraphic->Assign(TempElement.GraphicPtr);
2981  TempGraphic->Transparent = true;
2982  TempGraphic->TransparentColor = Utilities->clTransparent;
2983  if(RouteType == TAllRoutes::AutoSigsRoute)
2984  {
2985  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2986  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
2987  }
2988  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
2989  }
2990  }
2991  else
2992  {
2993  TempGraphic->Assign(TempElement.GraphicPtr);
2994  TempGraphic->Transparent = true;
2995  TempGraphic->TransparentColor = Utilities->clTransparent;
2996  if(RouteType == TAllRoutes::AutoSigsRoute)
2997  {
2998  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
2999  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3000  }
3001  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3002  }
3003  }
3004  delete TempGraphic;
3005  Utilities->CallLogPop(675);
3006 }
3007 
3008 // ---------------------------------------------------------------------------
3009 
3010 // This was an attempt to pick up the actual 8x8 graphic from the display, so that text & user graphics would show as soon as the train passed, and overwrite it with the
3011 // reconstructed track, and it works ok but for the little arrows showing route directions at start and end, which extend beyond the track. It doesn't matter for autosig
3012 // routes because they are replotted (alomg with the direction arrows) but for others they shouldn't be. Leave in in case an easy way to remove these pointers comes to mind.
3013 /*
3014 
3015  void TTrain::PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
3016  {
3017  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PickUpBackgroundBitmap," + AnsiString(HOffset) +
3018  "," + AnsiString(VOffset) + "," + AnsiString(Element) + "," + AnsiString(EntryPos) + "," + HeadCode);
3019  TAllRoutes::TRouteType RouteType;
3020  Graphics::TBitmap *EXGraphicPtr = RailGraphics->bmTransparentBgnd;
3021  // default values
3022  Graphics::TBitmap *EntryDirectionGraphicPtr = RailGraphics->bmTransparentBgnd;
3023  TTrackElement TempElement = Track->TrackElementAt(, Element); //this is a copy of the element passed into the function
3024  TRect SourceRect, DestRect, ScreenSourceRect;
3025  RouteType = AllRoutes->GetRouteTypeAndGraphics(7, Element, EntryPos, EXGraphicPtr, EntryDirectionGraphicPtr);
3026 
3027  DestRect.init(0, 0, 8, 8); //initialise left, top, right, bottom
3028  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3029  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3030 
3031  //add text & user graphics if any to *GraphicPtr prior to adding the track
3032  int Left = ((TempElement.HLoc - Display->DisplayOffsetH) * 16) + HOffset;
3033  int Top = ((TempElement.VLoc - Display->DisplayOffsetV) * 16) + VOffset;
3034  int Right = Left + 8;
3035  int Bottom = Top + 8;
3036  ScreenSourceRect.init(Left, Top, Right, Bottom);
3037  GraphicPtr->Canvas->CopyMode = cmSrcCopy;
3038  GraphicPtr->Canvas->CopyRect(DestRect, Display->GetImage()->Canvas, ScreenSourceRect);
3039 
3040  Graphics::TBitmap *TempGraphic = new Graphics::TBitmap; //this will hold the 16x16 reconstructed element, prior to transfer of the 8x8 bit to *GraphicPtr
3041  TempGraphic->PixelFormat = pf8bit;
3042  TempGraphic->Width = 16;
3043  TempGraphic->Height = 16;
3044 
3045  Graphics::TBitmap *SourceGraphic = new Graphics::TBitmap; //this will hold the 8x8 element segment from TempGraphic - needed because to keep transparency have to use Draw, not CopyRect
3046  SourceGraphic->PixelFormat = pf8bit;
3047  SourceGraphic->Width = 16;
3048  SourceGraphic->Height = 16;
3049  SourceGraphic->Transparent = true;
3050  SourceGraphic->TransparentColor = Utilities->clTransparent;
3051 
3052  if (TempElement.TrackType == Points)
3053  {
3054  TempGraphic->Assign(TempElement.GraphicPtr);
3055  TempGraphic->Transparent = true;
3056  TempGraphic->TransparentColor = Utilities->clTransparent;
3057  if (RouteType == TAllRoutes::AutoSigsRoute)
3058  {
3059  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3060  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3061  }
3062  else
3063  {
3064  TempGraphic->Canvas->Draw(0, 0, Track->GetFilletGraphic(1, TempElement)); // add fillet
3065  }
3066  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3067  }
3068  else if (TempElement.TrackType == GapJump) // plot set gap
3069  {
3070  if (TempElement.SpeedTag == 88)
3071  TempGraphic->Assign(RailGraphics->gl88set);
3072  else if (TempElement.SpeedTag == 89)
3073  TempGraphic->Assign(RailGraphics->gl89set);
3074  else if (TempElement.SpeedTag == 90)
3075  TempGraphic->Assign(RailGraphics->gl90set);
3076  else if (TempElement.SpeedTag == 91)
3077  TempGraphic->Assign(RailGraphics->gl91set);
3078  else if (TempElement.SpeedTag == 92)
3079  TempGraphic->Assign(RailGraphics->gl92set);
3080  else if (TempElement.SpeedTag == 93)
3081  TempGraphic->Assign(RailGraphics->bm93set);
3082  else if (TempElement.SpeedTag == 94)
3083  TempGraphic->Assign(RailGraphics->bm94set);
3084  else if (TempElement.SpeedTag == 95)
3085  TempGraphic->Assign(RailGraphics->gl95set);
3086  TempGraphic->Transparent = true;
3087  TempGraphic->TransparentColor = Utilities->clTransparent;
3088  if (RouteType == TAllRoutes::AutoSigsRoute) {
3089  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3090  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3091  }
3092  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3093  }
3094  // new for version 0.6
3095  else if (TempElement.TrackType == SignalPost)
3096  {
3097  if (TempElement.SigAspect == TTrackElement::GroundSignal)
3098  {
3099  for (int x = 0; x < 40; x++)
3100  {
3101  if ((Track->SigTableGroundSignal[x].SpeedTag == TempElement.SpeedTag) && (Track->SigTableGroundSignal[x].Attribute == 0))
3102  // need to stop aspect
3103  {
3104  TempGraphic->Assign(Track->SigTableGroundSignal[x].SigPtr);
3105  break;
3106  }
3107  }
3108  }
3109  else // normal signal
3110  {
3111  TempGraphic->Assign(TempElement.GraphicPtr);
3112  // GraphicPtr set to normal signal in a signal track element
3113  }
3114  TempGraphic->Transparent = true;
3115  TempGraphic->TransparentColor = Utilities->clTransparent;
3116  if (RouteType == TAllRoutes::AutoSigsRoute) {
3117  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3118  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3119  }
3120  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3121  }
3122  else {
3123  // first check if there's a NamedNonStationLocation element at that position & if so pick up that as the background
3124  // can't name points gaps or signals so 'else' OK
3125  bool FoundFlag;
3126  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap
3127  (4, TempElement.HLoc, TempElement.VLoc, FoundFlag);
3128  if (FoundFlag)
3129  {
3130  if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == NamedNonStationLocation)
3131  {
3132  GraphicPtr->Canvas->CopyRect(DestRect,
3133  Track->InactiveTrackElementAt(, IMPair.first).GraphicPtr->Canvas, SourceRect);
3134  TempGraphic->Assign(RailGraphics->bmName);
3135  TempGraphic->Transparent = true;
3136  TempGraphic->TransparentColor = Utilities->clTransparent;
3137  if (RouteType == TAllRoutes::AutoSigsRoute)
3138  {
3139  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3140  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3141  }
3142  else
3143  TempGraphic->Canvas->Draw(0, 0, TempElement.GraphicPtr);
3144  // draw track on top
3145  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3146  SourceRect);
3147  }
3148  else if (Track->InactiveTrackElementAt(, IMPair.first).TrackType == LevelCrossing) {
3149  TempGraphic->Assign(TempElement.GraphicPtr);
3150  TempGraphic->Transparent = true;
3151  TempGraphic->TransparentColor = Utilities->clTransparent;
3152  // note that can't be an AutoSigsRoute
3153  // now overlay the LC central portion
3154  TempGraphic->Canvas->Draw(0, 0, RailGraphics->LCPlain);
3155  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3156  SourceRect);
3157  }
3158  else {
3159  TempGraphic->Assign(TempElement.GraphicPtr);
3160  TempGraphic->Transparent = true;
3161  TempGraphic->TransparentColor = Utilities->clTransparent;
3162  if (RouteType == TAllRoutes::AutoSigsRoute) {
3163  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3164  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3165  }
3166  GraphicPtr->Canvas->CopyRect(DestRect, TempGraphic->Canvas,
3167  SourceRect);
3168  }
3169  }
3170  else {
3171  TempGraphic->Assign(TempElement.GraphicPtr);
3172  TempGraphic->Transparent = true;
3173  TempGraphic->TransparentColor = Utilities->clTransparent;
3174  if (RouteType == TAllRoutes::AutoSigsRoute) {
3175  TempGraphic->Canvas->Draw(0, 0, EXGraphicPtr);
3176  TempGraphic->Canvas->Draw(0, 0, EntryDirectionGraphicPtr);
3177  }
3178  SourceGraphic->Canvas->CopyRect(DestRect, TempGraphic->Canvas, SourceRect);
3179  GraphicPtr->Canvas->Draw(0, 0, SourceGraphic);
3180  }
3181  }
3182  delete TempGraphic;
3183  delete SourceGraphic;
3184  Utilities->CallLogPop();
3185  }
3186 */
3187 // ---------------------------------------------------------------------------
3188 
3189 void TTrain::PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
3190 {
3191  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainGraphic," + AnsiString(ArrayNumber) + "," + HeadCode);
3192  if(PlotElement[ArrayNumber] == -1)
3193  {
3194  Utilities->CallLogPop(676);
3195  return; // not plotted yet
3196  }
3197  SetTrainElementID(0, PlotElement[ArrayNumber], PlotEntryPos[ArrayNumber]);
3198  // set before plot so gap flashing stops first
3199  Disp->PlotOutput(29, ((Track->TrackElementAt(295, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3200  ((Track->TrackElementAt(296, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), HeadCodePosition[ArrayNumber]);
3201  // Only need to set ID for leading element, stays set until train finally leaves the element
3202  Plotted = true;
3203  Utilities->CallLogPop(677);
3204 }
3205 
3206 // ---------------------------------------------------------------------------
3207 
3208 void TTrain::PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
3209 {
3210  Disp->PlotOutput(30, ((Track->TrackElementAt(297, PlotElement[ArrayNumber]).HLoc * 16) + HOffset[ArrayNumber]),
3211  ((Track->TrackElementAt(298, PlotElement[ArrayNumber]).VLoc * 16) + VOffset[ArrayNumber]), BackgroundPtr[ArrayNumber]);
3212 }
3213 
3214 // ---------------------------------------------------------------------------
3215 
3216 bool TTrain::BufferAtExit(int Caller, int Element, int ExitPos) const
3217 {
3218  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BufferAtExit," + AnsiString(Element) + "," + AnsiString(ExitPos) + "," +
3219  HeadCode);
3220  if((Track->TrackElementAt(299, Element).TrackType == Buffers) && (Track->TrackElementAt(300, Element).Config[ExitPos] == End))
3221  {
3222  Utilities->CallLogPop(678);
3223  return(true);
3224  }
3225  else
3226  {
3227  Utilities->CallLogPop(679);
3228  return(false);
3229  }
3230 }
3231 
3232 // ---------------------------------------------------------------------------
3233 
3234 bool TTrain::ContinuationExit(int Caller, int Element, int ExitPos) const
3235 {
3236  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationExit," + AnsiString(Element) + "," + AnsiString(ExitPos) +
3237  "," + HeadCode);
3238  if((Track->TrackElementAt(301, Element).TrackType == Continuation) && (Track->TrackElementAt(302, Element).Config[ExitPos] == End))
3239  {
3240  Utilities->CallLogPop(680);
3241  return(true);
3242  }
3243  else
3244  {
3245  Utilities->CallLogPop(681);
3246  return(false);
3247  }
3248 }
3249 
3250 // ---------------------------------------------------------------------------
3251 
3252 bool TTrain::IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
3253 // test whether this train on a bridge on trackpos 0 & 1
3254 {
3255  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos01," + AnsiString(TrackVectorPosition) + "," +
3256  HeadCode);
3257  if(Track->TrackElementAt(303, TrackVectorPosition).TrackType != Bridge)
3258  {
3259  Utilities->CallLogPop(682);
3260  return(false);
3261  }
3262  // if(Track->TrackElementAt(304, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3264  {
3266  {
3267  throw Exception("Error, same train on two different bridge tracks");
3268  }
3269  else
3270  {
3271  Utilities->CallLogPop(684);
3272  return(true);
3273  }
3274  }
3275  else
3276  {
3277  Utilities->CallLogPop(685);
3278  return(false);
3279  }
3280 }
3281 
3282 // ---------------------------------------------------------------------------
3283 
3284 bool TTrain::IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
3285 // test whether this train on a bridge on trackpos 2 & 3
3286 {
3287  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainIDOnBridgeTrackPos23," + AnsiString(TrackVectorPosition) + "," +
3288  HeadCode);
3289  if(Track->TrackElementAt(307, TrackVectorPosition).TrackType != Bridge)
3290  {
3291  Utilities->CallLogPop(686);
3292  return(false);
3293  }
3294  // if(Track->TrackElementAt(308, TrackVectorPosition).TrainIDOnElement != TrainID) return false; No, if a bridge could be one of 2 TrainIDs
3296  {
3297  // don't carry out check for train on tracks 0 & 1 else will enter an infinite loop if train on both
3298  Utilities->CallLogPop(687);
3299  return(true);
3300  }
3301  else
3302  {
3303  Utilities->CallLogPop(688);
3304  return(false);
3305  }
3306 }
3307 
3308 // ---------------------------------------------------------------------------
3309 
3310 void TTrain::SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3311 {
3312  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3313  AnsiString(EntryPos) + "," + HeadCode);
3314  Track->TrackElementAt(310, TrackVectorPosition).TrainIDOnElement = TrainID;
3315 
3316  // unplot GapFlash graphics if land on flashing gap (this done before train plotted - see PlotTrainGraphic)
3317  if(Track->GapFlashFlag)
3318  {
3320  {
3323  Track->GapFlashFlag = false;
3324  }
3325  }
3326  if(Track->TrackElementAt(311, TrackVectorPosition).TrackType == Bridge)
3327  {
3328  if(EntryPos == -1)
3329  {
3330  throw Exception("Error, TrackVectorPosition set but not EntryPos in SetTrainElementID");
3331  }
3332  if(EntryPos < 2)
3333  {
3335  }
3336  else
3337  {
3339  }
3340  }
3341  Utilities->CallLogPop(690);
3342 }
3343 
3344 // ---------------------------------------------------------------------------
3345 
3346 void TTrain::ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
3347 {
3348  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ResetTrainElementID," + AnsiString(TrackVectorPosition) + "," +
3349  AnsiString(EntryPos) + "," + HeadCode);
3350  if(Track->TrackElementAt(314, TrackVectorPosition).TrackType != Bridge)
3351  {
3352  Track->TrackElementAt(315, TrackVectorPosition).TrainIDOnElement = -1;
3353  }
3354  else
3355  {
3356  if(EntryPos == -1)
3357  {
3358  throw Exception("Error, TrackVectorPosition set but not EntryPos in ResetTrainElementID");
3359  }
3360  if(EntryPos < 2)
3361  {
3362  Track->TrackElementAt(316, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 = -1;
3363  }
3364  else
3365  {
3366  Track->TrackElementAt(317, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 = -1;
3367  }
3368  if((EntryPos < 2) && (Track->TrackElementAt(318, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
3369  // i.e. other train on track 2&3
3370  {
3371  Track->TrackElementAt(319, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(320, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
3372  }
3373  else if((EntryPos > 1) && (Track->TrackElementAt(321, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
3374  // i.e. other train on track 1&2
3375  {
3376  Track->TrackElementAt(322, TrackVectorPosition).TrainIDOnElement = Track->TrackElementAt(323, TrackVectorPosition).TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
3377  }
3378  else
3379  {
3380  Track->TrackElementAt(324, TrackVectorPosition).TrainIDOnElement = -1;
3381  }
3382  }
3383  Utilities->CallLogPop(691);
3384 }
3385 
3386 // ---------------------------------------------------------------------------
3387 
3388 void TTrain::PlotAlternativeTrackRouteGraphic(int Caller, unsigned int ElementVecNum, int ElementEntryPos, int HOffset, int VOffset, TStraddle StraddleValue)
3389 {
3390  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotAlternativeTrackRouteGraphic," + AnsiString(ElementVecNum) + "," +
3391  AnsiString(ElementEntryPos) + "," + AnsiString(HOffset) + "," + AnsiString(VOffset) + "," + AnsiString(StraddleValue) + "," + HeadCode);
3392  int LockedVectorNumber;
3393 
3394  if(Track->TrackElementAt(325, ElementVecNum).TrackType != Bridge)
3395  // && (Track->TrackElementAt(326, ElementVecNum).TrackType != Crossover))
3396  {
3397  // only applies for a bridge as there can't be (or shouldn't be) 2 routes on an element that isn't a bridge
3398  Utilities->CallLogPop(692);
3399  return;
3400  }
3401  if(AllRoutes->TrackIsInARoute(0, ElementVecNum, (3 - ElementEntryPos)))
3402  // i.e other track is in a marked route
3403  // LinkPos doesn't have to be the entry position for the above check
3404  {
3405  TRect SourceRect, DestRect;
3406  DestRect.init(0, 0, 8, 8); // left, top, right, bottom
3407  // note right and bottom rect co-ordinates are 1 greater than the pixel area
3408  SourceRect.init(HOffset, VOffset, HOffset + 8, VOffset + 8);
3409  // identify the route element for the other track
3410  TAllRoutes::TRouteElementPair RoutePair1, RoutePair2;
3411  RoutePair1 = AllRoutes->GetRouteElementDataFromRoute2MultiMap(13, Track->TrackElementAt(327, ElementVecNum).HLoc,
3412  Track->TrackElementAt(328, ElementVecNum).VLoc, RoutePair2);
3413  int FirstELink, SecondELink = -1;
3414  FirstELink = AllRoutes->GetFixedRouteAt(149, RoutePair1.first).GetFixedPrefDirElementAt(159, RoutePair1.second).GetELink();
3415  // must be at least one
3416  if(RoutePair2.first > -1)
3417  {
3418  SecondELink = AllRoutes->GetFixedRouteAt(150, RoutePair2.first).GetFixedPrefDirElementAt(160, RoutePair2.second).GetELink();
3419  }
3420  TPrefDirElement RouteElement;
3421  // Graphics::TBitmap *RouteGraphic;
3422  if(FirstELink == Track->TrackElementAt(329, ElementVecNum).Link[ElementEntryPos])
3423  // i.e. other track is in RoutePair2
3424  {
3425  if(SecondELink == -1)
3426  {
3427  throw Exception("Error - Second ELink should be set but isn't in PlotAlternativeTrackRouteGraphic [1]");
3428  }
3429  if(SecondELink == Track->TrackElementAt(330, ElementVecNum).Link[ElementEntryPos])
3430  // error if both have same Link number
3431  {
3432  throw Exception("Error - First & Second ELinks have same value in PlotAlternativeTrackRouteGraphic");
3433  }
3434  // RouteGraphic = AllRoutes->GetFixedRouteAt(151, RoutePair2.first).GetFixedPrefDirElementAt(161, RoutePair2.second).GetEXGraphicPtr();
3435  RouteElement = AllRoutes->GetFixedRouteAt(152, RoutePair2.first).GetFixedPrefDirElementAt(162, RoutePair2.second);
3436  }
3437  else // other track is in RoutePair1
3438  {
3439  // RouteGraphic = AllRoutes->GetFixedRouteAt(153, RoutePair1.first).GetFixedPrefDirElementAt(163, RoutePair1.second).GetEXGraphicPtr();
3440  RouteElement = AllRoutes->GetFixedRouteAt(154, RoutePair1.first).GetFixedPrefDirElementAt(164, RoutePair1.second);
3441  }
3442  Graphics::TBitmap *DestGraphic = new Graphics::TBitmap;
3443  DestGraphic->PixelFormat = pf8bit;
3444  DestGraphic->Width = 8;
3445  DestGraphic->Height = 8;
3446  DestGraphic->Transparent = true;
3447  // has to be transparent or will overwrite the track that the train has just left
3448  DestGraphic->TransparentColor = Utilities->clTransparent;
3449  DestGraphic->Canvas->CopyRect(DestRect, RouteElement.GetRouteEXGraphicPtr()->Canvas, SourceRect);
3450  Display->PlotOutput(31, (Track->TrackElementAt(331, ElementVecNum).HLoc * 16) + HOffset,
3451  (Track->TrackElementAt(332, ElementVecNum).VLoc * 16) + VOffset, DestGraphic);
3452  // plot locked route marker for other route if appropriate
3453  TPrefDirElement PrefDirElement; // holder for next call, unused
3454  // plot locked route marker if appropriate, but only when train leaves element completely as this is a 16x16 graphic
3455  if(StraddleValue == LeadMidLag)
3456  {
3458  PrefDirElement, LockedVectorNumber))
3459  {
3460  Display->PlotOutput(32, (Track->TrackElementAt(333, ElementVecNum).HLoc * 16), (Track->TrackElementAt(334, ElementVecNum).VLoc * 16),
3461  RailGraphics->LockedRouteCancelPtr[RouteElement.GetELink()]);
3462  }
3463  }
3464  delete DestGraphic;
3465  }
3466  // but - there may be a train on the other track - if so need to replot it else the section of route overwrites it
3467  // also can only be a bridge or trains either have already or soon will crash
3468  if(Track->TrackElementAt(335, ElementVecNum).TrackType != Bridge)
3469  {
3470  Utilities->CallLogPop(695);
3471  return;
3472  }
3473  if(ElementEntryPos > 1) // other train is on track 01
3474  {
3476  {
3478  }
3479  }
3480  else // other train is on track 23
3481  {
3483  {
3485  }
3486  }
3487  Utilities->CallLogPop(696);
3488 }
3489 
3490 // ---------------------------------------------------------------------------
3491 
3492 void TTrain::CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
3493 {
3494  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndCancelRouteForWrongEndEntry," + AnsiString(Element) + "," +
3495  AnsiString(EntryPos) + "," + HeadCode);
3496  int RouteNumber;
3497  bool WrongRoute = false;
3498  TPrefDirElement RouteElement;
3500  TAllRoutes::TRoute2MultiMapIterator Route2MultiMapIterator;
3501 
3502  if(AllRoutes->GetRouteTypeAndNumber(11, Element, EntryPos, RouteNumber) == TAllRoutes::NoRoute)
3503  // here if single track element & no route, or double track element with no route at EntryPos but still need to check if on points or a crossover on non-route track,
3504  // and force-erase route if so (bridge OK of course) note that GetRouteTypeAndNumber allows for points having an EntryPos of 0 or 2 & still returns correct values
3505  {
3506  if((Track->TrackElementAt(340, Element).TrackType == Crossover) || (Track->TrackElementAt(341, Element).TrackType == Points))
3507  {
3508  if(AllRoutes->GetRouteTypeAndNumber(12, Element, (3 - EntryPos), RouteNumber) != TAllRoutes::NoRoute)
3509  // (3-EntryPos) guarantees other route (0->3; 1->2; 2->1; 3->0)
3510  {
3511  if(AllRoutes->GetFixedRouteAt(179, RouteNumber).PrefDirSize() > 2)
3512  {
3513  // don't call for stub end routes
3515  }
3516  AllRoutes->GetModifiableRouteAt(13, RouteNumber).ForceCancelRoute(1);
3517  Utilities->CallLogPop(697);
3518  return;
3519  }
3520  }
3521  // also need to check for a route on a crossing diagonal
3522  TTrackElement TrackElement = Track->TrackElementAt(892, Element);
3523  int LinkNumber = TrackElement.Link[EntryPos];
3524  if((LinkNumber == 1) || (LinkNumber == 3) || (LinkNumber == 7) || (LinkNumber == 9))
3525  {
3526  if(AllRoutes->DiagonalFouledByRoute(0, TrackElement.HLoc, TrackElement.VLoc, LinkNumber))
3527  {
3528  // for LinkNumber = 1, potentially fouled diagonals are at H-1, V, Lk 3 & H, V-1, Lk 7
3529  bool LogActionErrorCalled = false;
3530  // to ensure only called once if have 2 routes on the 2 crossed diagonals
3531  if(LinkNumber == 1)
3532  {
3533  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(0, TrackElement.HLoc - 1, TrackElement.VLoc, 3, RouteNumber))
3534  {
3535  if(AllRoutes->GetFixedRouteAt(207, RouteNumber).PrefDirSize() > 2)
3536  {
3537  // don't call for stub end routes
3539  LogActionErrorCalled = true;
3540  }
3541  AllRoutes->GetModifiableRouteAt(20, RouteNumber).ForceCancelRoute(3);
3542  }
3543  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(1, TrackElement.HLoc, TrackElement.VLoc - 1, 7, RouteNumber))
3544  // not else in case have different routes on each diagonal, though shouldn't be possible
3545  {
3546  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(208, RouteNumber).PrefDirSize() > 2)
3547  {
3548  // don't call for stub end routes
3550  }
3551  AllRoutes->GetModifiableRouteAt(21, RouteNumber).ForceCancelRoute(4);
3552  }
3553  }
3554 
3555  // for LinkNumber = 3, potentially fouled diagonals are at H+1, V, Lk 1 & H, V-1 Lk 9
3556  else if(LinkNumber == 3)
3557  {
3558  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(2, TrackElement.HLoc + 1, TrackElement.VLoc, 1, RouteNumber))
3559  {
3560  if(AllRoutes->GetFixedRouteAt(209, RouteNumber).PrefDirSize() > 2)
3561  {
3562  // don't call for stub end routes
3564  LogActionErrorCalled = true;
3565  }
3566  AllRoutes->GetModifiableRouteAt(22, RouteNumber).ForceCancelRoute(5);
3567  }
3568  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(3, TrackElement.HLoc, TrackElement.VLoc - 1, 9, RouteNumber))
3569  // not else in case have different routes on each diagonal, though shouldn't be possible
3570  {
3571  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(210, RouteNumber).PrefDirSize() > 2)
3572  {
3573  // don't call for stub end routes
3575  }
3576  AllRoutes->GetModifiableRouteAt(23, RouteNumber).ForceCancelRoute(6);
3577  }
3578  }
3579 
3580  // for LinkNumber = 7, potentially fouled diagonals are at H-1, V, Lk 9 & H, V+1 Lk 1
3581  else if(LinkNumber == 7)
3582  {
3583  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(4, TrackElement.HLoc - 1, TrackElement.VLoc, 9, RouteNumber))
3584  {
3585  if(AllRoutes->GetFixedRouteAt(211, RouteNumber).PrefDirSize() > 2)
3586  {
3587  // don't call for stub end routes
3589  LogActionErrorCalled = true;
3590  }
3591  AllRoutes->GetModifiableRouteAt(24, RouteNumber).ForceCancelRoute(7);
3592  }
3593  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(5, TrackElement.HLoc, TrackElement.VLoc + 1, 1, RouteNumber))
3594  // not else in case have different routes on each diagonal, though shouldn't be possible
3595  {
3596  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(212, RouteNumber).PrefDirSize() > 2)
3597  {
3598  // don't call for stub end routes
3600  }
3601  AllRoutes->GetModifiableRouteAt(25, RouteNumber).ForceCancelRoute(8);
3602  }
3603  }
3604 
3605  // for LinkNumber = 9, potentially fouled diagonals are at H+1, V, Lk 7 & H, V+1 Lk 3
3606  else if(LinkNumber == 9)
3607  {
3608  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(6, TrackElement.HLoc + 1, TrackElement.VLoc, 7, RouteNumber))
3609  {
3610  if(AllRoutes->GetFixedRouteAt(213, RouteNumber).PrefDirSize() > 2)
3611  {
3612  // don't call for stub end routes
3614  LogActionErrorCalled = true;
3615  }
3616  AllRoutes->GetModifiableRouteAt(26, RouteNumber).ForceCancelRoute(9);
3617  }
3618  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(7, TrackElement.HLoc, TrackElement.VLoc + 1, 3, RouteNumber))
3619  // not else in case have different routes on each diagonal, though shouldn't be possible
3620  {
3621  if(!LogActionErrorCalled && AllRoutes->GetFixedRouteAt(214, RouteNumber).PrefDirSize() > 2)
3622  {
3623  // don't call for stub end routes
3625  }
3626  AllRoutes->GetModifiableRouteAt(27, RouteNumber).ForceCancelRoute(10);
3627  }
3628  }
3629  }
3630  }
3631  Utilities->CallLogPop(698);
3632  return; // no route on other track or no other track
3633  }
3634  // here if there is a route at Element & EntryPos - so there can't be a route on the other track if a 4 track element, unless it's a bridge & that's ok
3635  for(unsigned int x = 0; x < AllRoutes->GetFixedRouteAt(155, RouteNumber).PrefDirSize(); x++)
3636  {
3637  RouteElement = AllRoutes->GetFixedRouteAt(156, RouteNumber).GetFixedPrefDirElementAt(165, x);
3638  bool PointsAtElement = (Track->TrackElementAt(987, Element).TrackType == Points); // new at v2.4.2 for points check - Xeon repoted it 30/05/20. He found that for routes that
3639  if(RouteElement.GetTrackVectorPosition() == (unsigned int)Element) // cross bridges at both levels can have entrypos 0 & other exitpos 2 so if don't have this check can cancel a route wrongly
3640  {
3641  if(RouteElement.GetELinkPos() == EntryPos)
3642  {
3643  Utilities->CallLogPop(699);
3644  return; // right direction
3645  }
3646  else if((RouteElement.GetELinkPos() == 2) && (EntryPos == 0) && PointsAtElement)
3647  {
3648  Utilities->CallLogPop(700);
3649  return; // right direction (points)
3650  }
3651  else if((RouteElement.GetELinkPos() == 0) && (EntryPos == 2) && PointsAtElement)
3652  {
3653  Utilities->CallLogPop(701);
3654  return; // right direction (points)
3655  }
3656  else if(RouteElement.GetXLinkPos() == EntryPos)
3657  {
3658  WrongRoute = true;
3659  break; // wrong direction
3660  }
3661  else if((RouteElement.GetXLinkPos() == 2) && (EntryPos == 0) && PointsAtElement) // ok for bridges
3662  {
3663  WrongRoute = true;
3664  break; // wrong direction
3665  }
3666  else if((RouteElement.GetXLinkPos() == 0) && (EntryPos == 2) && PointsAtElement) // ok for bridges
3667  {
3668  WrongRoute = true;
3669  break; // wrong direction
3670  }
3671  }
3672  }
3673  if(!WrongRoute)
3674  {
3675  throw Exception("Error, Element in route but no route found in CheckAndCancelRouteForWrongEndEntry");
3676  }
3677  if(AllRoutes->GetFixedRouteAt(180, RouteNumber).PrefDirSize() > 2)
3678  {
3679  // don't call for stub end routes
3681  }
3682  AllRoutes->GetModifiableRouteAt(14, RouteNumber).ForceCancelRoute(2);
3683  Utilities->CallLogPop(703);
3684 }
3685 
3686 // ---------------------------------------------------------------------------
3687 
3688 void TTrain::PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
3689 {
3690  if(BackgroundColour == NewBackgroundColour)
3691  {
3692  return; // don't replot if already correct
3693 
3694  }
3695  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainWithNewBackgroundColour," + AnsiString(NewBackgroundColour));
3696  bool ColourError = false, ColourError2 = false;
3697 
3698  RailGraphics->ChangeBackgroundColour(1, FrontCodePtr, FrontCodePtr, NewBackgroundColour, BackgroundColour, ColourError);
3699  if(ColourError)
3700  {
3701  ColourError2 = true;
3702  }
3703  for(int x = 0; x < 4; x++)
3704  {
3705  RailGraphics->ChangeBackgroundColour(2, HeadCodeGrPtr[x], HeadCodeGrPtr[x], NewBackgroundColour, BackgroundColour, ColourError);
3706  if(ColourError)
3707  {
3708  ColourError2 = true;
3709  }
3710  }
3711  if(ColourError2)
3712  {
3714  "ERROR: Colour depth insufficient to display train colours properly. Please ensure that the 'safe' (web) palette of 256 colours can be displayed");
3715  }
3716  // NB need a separate 'for' loop since the plot order can be different from the graphic order depending on the direction
3717  // of motion
3718  for(int x = 0; x < 4; x++)
3719  {
3720  PlotTrainGraphic(6, x, Disp);
3721  }
3722  BackgroundColour = NewBackgroundColour;
3723  Display->Update();
3724  // need to keep this since Update() not called for PlotSmallOutput as too slow
3725  Utilities->CallLogPop(704);
3726 }
3727 
3728 // ---------------------------------------------------------------------------
3729 
3730 void TTrain::SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
3731 /*
3732 Note: Within the loop BrakeRate can only increase and MaxExitSpeed can only reduce
3733 
3734 Summary: Called during PlotStartPosition to set initial values, when stopped and need to restart, and during UpdateTrain when Straddle is LeadMidLag,
3735 i.e. just as the front of a train is about to move fully onto an element, where TrackVectorPosition is the element immediately in front
3736 of the element the front of the train is moving fully on to. The function calculates the times and speeds at the next half-element and
3737 full-element moves.
3738 
3739 Detail: TrackVectorPosition & EntryPos correspond to the TrackVector element immediately in front of where the train is at
3740 the end of the current Update(). EntrySpeed is needed but this is a class data member so isn't passed in. Set the
3741 train BrakeRate to zero (for now, likely to be altered later), & check if zero entry speed with another train directly in front & if so
3742 remain stopped. Pick up the half length value and speed limit for the EntryPos track, and set FrontElementLength to the length of the
3743 EntryPos track, then set LimitingSpeed to the minimum of the element speed limit or the train's maximum speed. Check if running past a
3744 red signal and set SPADFlag if so (use 1 for EntrySpeed rather than 0 as this value is a double so could be slightly in excess of 0).
3745 In this case set the brake rate to maximum to stop as soon as possible.
3746 
3747 For no SPAD calculate the distance that will be travelled at the maximum speed at which the train can exit the next element at half
3748 MaxBrakeRate, this is DistanceAtHalfBraking (also calculate DistanceAtThreeQuarterBraking - used for stopping under signaller control).
3749 DistanceAtHalfBraking is used as the limiting forward look from the next element (i.e. following EntryPos)
3750 for computing the actual braking rate. If no more restrictive speed limits or reasons to stop are found within the forward look then the
3751 train can accelerate or stay at its (local) maximum speed for the next element. The maximum speed on exit from the next element is used
3752 for calculating the forward look because it represents the worst case - i.e. assumes that the train accelerates for the next element.
3753 
3754 A loop is now entered where the CumulativeLength is updated and each successive element (if there are any - current element checked
3755 first to see whether buffers or continuation) in turn is examined: first the length of the
3756 current element is added to the cumulative length; then the half length and speedlimit are set for the next element - points are
3757 followed according to their current setting (Attribute), but derailments are ignored as these are dealt with outside this function; checks
3758 are then made to see whether the next element is a red signal (train should stop before it); next element is a buffer (train should stop
3759 at the end of it so the cumulative length has the next element length added); current element is a buffer (train should stop
3760 at the end of the current element so no need to alter the cumulative length); or have reached a named location stop position. For any of
3761 these reasons, or if stopping under signaller control, there is no more looping, instead the braking rate is calculated to bring the train
3762 to a stop over CumulativeLength. For all normal purposes the braking rate will be less than half (light braking), or less than three
3763 quarters if stopping under signaller control (heavy braking). However if signals are reset in front of a train then the train may need
3764 emergency braking (> 90% max brake rate) and a SPAD may result. Similarly if points are chaged in front of a train that divert it into a
3765 siding then again emeregency braking may be necessary and a crash may result.
3766 
3767 If the train is due to stop then the function calculates the half and full times and speeds and returns. However the calculation depends
3768 on the conditions at entry. If the EntrySpeed is lower than MaxHalfSpeed and the EntryPos element is the one
3769 that the train has to stop at the end of, as it might be for example if train had been stopped at a signal and the next element is a
3770 buffer, then the train accelerates for half the element and brakes for the other half.
3771 Now the BrakeRate is calculated (limited to the MaxBrakeRate), but if it is less than a value calculated at an earlier pass round
3772 the loop then it retains its earlier value (may be due to a close speed restriction that requires more braking than a more distant stop
3773 requirement). The MaxExitSpeedAtHalfBraking (maximum speed at which the train can leave the current element and still stop when required
3774 at half the max braking rate) value is also calculated using EntrySpeed and CumulativeLength, but limiting it to the line speed limit or
3775 train MaxRunningSpeed whichever is the lower. If EntrySpeed > MaxExitSpeedAtHalfBraking then braking is required, so the half and full
3776 speed and time values for the current element are calculated using BrakeRate, EntrySpeed and CurrentElementHalfLength. If need to stop
3777 at the end of the current elemecumulativent for other than a red signal (SPADs can occur) then ExitSpeedFull is set to 0. It should be calculated
3778 as 0 anyway for other than a red signal but this makes sure. If EntrySpeed <= MaxExitSpeedAtHalfBraking then can calculate the half and
3779 full speed and time values for acceleration over the current element, but limit ExitSpeedHalf & Full to MaxExitSpeedAtHalfBraking or to
3780 the current element speed limit if necessary. Check whether ExitSpeedHalf <= EntrySpeed (+0.01 since it's a double) and use constant speed
3781 time values for Half & Full if so, but prior to this increase EntrySpeed if necessary to avoid a divide by zero error.
3782 
3783 If the train is not due to stop within the DistanceAtHalfBraking from the next element following EntryPos then the next element (if there
3784 is one) is checked to see if its speed limit is less than the current value of LimitingSpeed (which is the minimum of any earlier element's
3785 speed limit that has been examined within the loop and the train's MaxRunning speed), and if so LimitingSpeed is set down to it. Now
3786 the MaxExitSpeedAtHalfBraking is calculated, limiting it to LimitingSpeed if less, in case need to accelerate in the current element, in
3787 which case the exit speeds need to be limited to MaxExitSpeedAtHalfBraking. If EntrySpeed > LimitingSpeed then calculate the braking rate
3788 to bring the speed down to LimitingSpeed in CumulativeLength, keeping the existing BrakeRate value if lower and keeping it within
3789 MaxBrakeRate.
3790 
3791 Then, providing the current element isn't a buffer or continuation, the 'Current' values are updated from the 'Next' values ready for
3792 the next loop iteration. The loop is broken out of if the current element is a buffer or continuation, the next element is a
3793 continuation, or (CumulativeLength - FrontElementLength) >= DistanceAtHalfBraking.
3794 
3795 Now the final Half and Full values can be set for braking (if BrakeRate > 0.01), or accelerating - limiting the half and full exit speed
3796 values to MaxExitSpeedAtHalfBraking if necessary, and using constant speed time values if the exit speeds aren't much different to
3797 EntrySpeed and EntrySpeed > 0.01 (to avoid a divide by zero error).
3798 
3799 Note that in no circumstances will a train stop when straddling 3 elements, it will always be fully on two elements. This is ensured
3800 by UpdateTrain() which never sets any stop conditions unless the train is fully on 2 elements when that function returns, i.e. entered
3801 when Straddle == LeadMidLag
3802 */
3803 {
3804  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetTrainMovementValues," + AnsiString(TrackVectorPosition) + "," +
3805  AnsiString(EntryPos) + "," + HeadCode);
3806  int EntryHalfLength, CurrentElementHalfLength, NextElementHalfLength, CumulativeLength = 0, CurrentTrackVectorPosition = TrackVectorPosition;
3807  int DistanceAtHalfBraking, DistanceAtThreeQuarterBraking, ExitPos, NextTrackVectorPosition, NextEntryPos;
3808  bool RedSignalFlag = false, BuffersFlag = false, StationFlag = false, BuffersOrContinuationNowFlag = false, ContinuationNextFlag = false,
3809  TrainInFrontInSignallerModeFlag = false;
3810  double LimitingSpeed, FrontElementMaxSpeed, MaxExitSpeedAtHalfBrakingSquared, MaxExitSpeedAtHalfBraking, NextSpeedLimit, TempBrakeRate;
3811  double ExitSpeedHalfSquared, ExitSpeedFullSquared;
3812  bool SignallerStopRequired = false;
3813 
3815  // set high to begin with to avoid divide by zero errors on restart after stops, will be set lower later
3816 
3817  // Member variables: EntrySpeed, ExitSpeedHalf, ExitSpeedFull, MaxExitSpeed, BrakeRate, EntryTime, ExitTimeHalf, ExitTimeFull, FrontElementSpeedLimit, FrontElementLength;
3818 
3819  OneLengthAccelDecel = false;
3820  BrakeRate = 0;
3821  if(PowerAtRail < 1)
3822  {
3823  BrakeRate = CoastingBrakeRate; //brings train to a stop in 13km in 15min from starting speed of 100km/h (from research)
3824  }
3825 //find FrontElementLength & FrontElementSpeedLimit (these correspond to TrackVectorPosition input value);
3826  if(CurrentTrackVectorPosition > -1)
3827  {
3828  if(Track->TrackElementAt(855, CurrentTrackVectorPosition).TrackType == Points) // this test & section added at v0.6
3829  {
3830  if((EntryPos == 0) || (EntryPos == 2))
3831  {
3832  if(Track->TrackElementAt(856, CurrentTrackVectorPosition).Attribute == 0)
3833  {
3834  CurrentElementHalfLength = (Track->TrackElementAt(857, CurrentTrackVectorPosition).Length01) / 2;
3835  FrontElementSpeedLimit = Track->TrackElementAt(858, CurrentTrackVectorPosition).SpeedLimit01;
3836  }
3837  else
3838  {
3839  CurrentElementHalfLength = (Track->TrackElementAt(859, CurrentTrackVectorPosition).Length23) / 2;
3840  FrontElementSpeedLimit = Track->TrackElementAt(860, CurrentTrackVectorPosition).SpeedLimit23;
3841  }
3842  }
3843  else if(EntryPos == 1)
3844  {
3845  CurrentElementHalfLength = (Track->TrackElementAt(861, CurrentTrackVectorPosition).Length01) / 2;
3846  FrontElementSpeedLimit = Track->TrackElementAt(862, CurrentTrackVectorPosition).SpeedLimit01;
3847  }
3848  else // == 3
3849  {
3850  CurrentElementHalfLength = (Track->TrackElementAt(863, CurrentTrackVectorPosition).Length23) / 2;
3851  FrontElementSpeedLimit = Track->TrackElementAt(864, CurrentTrackVectorPosition).SpeedLimit23;
3852  }
3853  }
3854  else
3855  {
3856  if(EntryPos > 1)
3857  {
3858  CurrentElementHalfLength = (Track->TrackElementAt(348, CurrentTrackVectorPosition).Length23) / 2;
3859  FrontElementSpeedLimit = Track->TrackElementAt(349, CurrentTrackVectorPosition).SpeedLimit23;
3860  }
3861  else
3862  {
3863  CurrentElementHalfLength = (Track->TrackElementAt(350, CurrentTrackVectorPosition).Length01) / 2;
3864  FrontElementSpeedLimit = Track->TrackElementAt(351, CurrentTrackVectorPosition).SpeedLimit01;
3865  }
3866  }
3867  EntryHalfLength = CurrentElementHalfLength;
3868  FrontElementLength = 2 * CurrentElementHalfLength;
3869  }
3870  else
3871  {
3872  throw Exception("Error - CurrentTrackVectorPosition < 0 in SetTrainMovementValues");
3873  }
3874  if((CurrentElementHalfLength < 0) || (FrontElementSpeedLimit < 0))
3875  {
3876  throw Exception("Error - HalfLength or SpeedLimit < 0 in SetTrainMovementValues");
3877  }
3878  // check if zero entry speed with another train directly in front & if so remain stopped
3879  if((EntryPos > -1) && Track->OtherTrainOnTrack(2, CurrentTrackVectorPosition, EntryPos, TrainID) && (EntrySpeed < 1))
3880  {
3881  EntrySpeed = 0;
3882  ExitSpeedHalf = 0;
3883  ExitSpeedFull = 0;
3884  MaxExitSpeed = 0;
3885  BrakeRate = 0;
3886  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
3887  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
3888  StoppedForTrainInFront = true;
3889  TrainInFront = true;
3890  Utilities->CallLogPop(705);
3891  return;
3892  }
3893  // new at v2.4.0 - check for stopped and zero power
3894  if((EntrySpeed < 1) && PowerAtRail < 1)
3895  {
3896  EntrySpeed = 0;
3897  ExitSpeedHalf = 0;
3898  ExitSpeedFull = 0;
3899  MaxExitSpeed = 0;
3900  BrakeRate = 0;
3901  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
3902  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
3903  StoppedWithoutPower = true;
3904  Utilities->CallLogPop(2125);
3905  return;
3906  }
3907 //set LimitingSpeed & FrontElementMaxSpeed (internal values)
3908  if(BeingCalledOn)
3909  {
3910  LimitingSpeed = CallOnMaxSpeed;
3911  }
3912  else
3913  {
3914  LimitingSpeed = MaximumSpeedLimit;
3915  }
3916  if(LimitingSpeed > FrontElementSpeedLimit)
3917  {
3918  LimitingSpeed = FrontElementSpeedLimit;
3919  }
3920  if(LimitingSpeed > MaxRunningSpeed) //MaxRunningSpeed is set in AddTrain depending on timetable or signaller control mode
3921  {
3922  LimitingSpeed = MaxRunningSpeed;
3923  }
3924  FrontElementMaxSpeed = LimitingSpeed;
3925 
3926 /*
3927  for braking the deceleration rate is constant so the following formuli (Newton's Laws) are used:-
3928  (1) V^2/(3.6^2) = U^2/(3.6^2) - 2FS;
3929  (2) V/3.6 = U/3.6 - FT;
3930  (3) S = UT/3.6 - 0.5FT^2
3931  where(V = final speed in kph [km/h/3.6 = m/s], U = initial speed in km/h, F = deceleration rate in m/s/s, S = distance in m & T = time in secs)
3932 
3933  for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
3934  (4) V^2/(3.6^2) - U^2/(3.6^2) = A^2T;
3935  (5) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
3936  where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time in secs
3937  It's a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
3938 
3939  calc max speed that can attain on exit from next element (as could accelerate over next element) and use that speed to calc
3940  DistanceAtHalfBraking, if use actual speed may miss a stop requirement just outside look-ahead & accelerate, and at next calc
3941  be unable to stop or have hard acceleration followed immediately by hard braking, this speed makes for smoother operation
3942 */
3943 
3944 // check if running past a red signal without permission
3945  if((Track->TrackElementAt(352, CurrentTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(EntryPos)] == Signal) &&
3946  (Track->TrackElementAt(353, CurrentTrackVectorPosition).Attribute == 0) && (EntrySpeed > 1) && !AllowedToPassRedSignal &&
3947  !Track->TrackElementAt(1553, CurrentTrackVectorPosition).CallingOnSet)
3948  { //CallingOnSet added at v2.14.0
3949  SPADFlag = true; // user has to intervene to reset & restart after spad
3950  }
3951  if(!SPADFlag)
3952  {
3953  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
3954  // to begin with calc the maximum exit speed (assumes accelerating) and then reduce it if necessary
3955  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
3956  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
3957  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
3958  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
3959 
3960  double ExitSpeedAtMaxBraking;
3961  // below introduced at v2.4.0, was ExitSpeedFull = LimitingSpeed; but that allowed very high brake rates when
3962  // took signaller control of a fast failed train with signaller limiting speed 30km/h
3964  {
3965  ExitSpeedAtMaxBraking = 0;
3966  }
3967  else
3968  {
3969  ExitSpeedAtMaxBraking = sqrt((EntrySpeed * EntrySpeed) - 2 * MaxBrakeRate * FrontElementLength);
3970  }
3971  double SpeedToUse;
3972  // use the highest of LimitingSpeed or ExitSpeedAtMaxBraking - added at v2.4.2 because trains entering at a continuation with zero (or very low) speed
3973  // & 2 elements before signal caused ExitSpeedAtMaxBraking & hence DistanceAtHalfBraking and DistanceAtThreeQuarterBraking to be zero, so no restriction was recognised
3974  // for first element & train accelerated at maximum rate, then at 2nd element train couldn't brake in time and overran the signal - notified by Micke via Discord on 02/06/20
3975  if(ExitSpeedAtMaxBraking > LimitingSpeed)
3976  {
3977  SpeedToUse = ExitSpeedAtMaxBraking;
3978  }
3979  else
3980  {
3981  SpeedToUse = LimitingSpeed;
3982  }
3983  if(ExitSpeedFull > SpeedToUse)
3984  {
3985  ExitSpeedFull = SpeedToUse;
3986  }
3987  DistanceAtHalfBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / MaxBrakeRate;
3988  DistanceAtThreeQuarterBraking = ExitSpeedFull * ExitSpeedFull / 3.6 / 3.6 / 1.5 / MaxBrakeRate; // used for signaller stops
3989 
3990  //now enter a do loop to examine each element in turn from the front of the train to calc the cumulative length and to see if a stop is required (flag set if so -
3991  //RedSignalFlag, BuffersFlag, StationFlag, TrainInFrontInSignallerModeFlag, SignallerStopRequired, StepForwardFlag) in which case there are no more loops
3992  // break out of the loop when ((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking ) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) || SignallerStoppingFlag);
3993 
3994  do
3995  {
3996  RedSignalFlag = false;
3997  BuffersFlag = false;
3998  StationFlag = false;
3999  BuffersOrContinuationNowFlag = false;
4000  ContinuationNextFlag = false;
4001  // have to reset this after the above test
4002  // add current element length to CumulativeLength
4003  CumulativeLength += (2 * CurrentElementHalfLength);
4004  if((CumulativeLength >= DistanceAtThreeQuarterBraking) && (TrainMode == Signaller) && SignallerStoppingFlag)
4005  {
4006  SignallerStopRequired = true;
4007  // once set stays set until SignallerStoppingFlag reset, providing !BuffersOrContinuationNowFlag,
4008  // set SignallerStopBrakeRate to stop in CumulativeLength unless already higher (i.e. can only increase)
4009  double TempBR = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4010  if(SignallerStopBrakeRate < TempBR)
4011  {
4012  SignallerStopBrakeRate = TempBR;
4013  }
4014  }
4015  // first check for stops within the length of the current element, where don't want any more checks & don't want
4016  // to add in any extra to the CumulativeLength. Only applies for buffers & station stops as signals should have been caught
4017  // during the last loop when the NextTrackVectorPosition was the signal.
4018 
4019  // check if current element is a buffer
4020  if(Track->TrackElementAt(374, CurrentTrackVectorPosition).TrackType == Buffers)
4021  {
4022  // no need to add in the length of this element to CumulativeLength as already included
4023  BuffersFlag = true;
4024  }
4025  // check if current element is a station stop
4026  if(TrainMode == Timetable)
4027  {
4028  bool StopRequired = false;
4029  if(!TimetableFinished && (NameInTimetableBeforeCDT(12, Track->TrackElementAt(375, CurrentTrackVectorPosition).ActiveTrackElementName,
4030  StopRequired) > -1) && ((Track->TrackElementAt(376, CurrentTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) ||
4031  (Track->TrackElementAt(377, CurrentTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) ||
4032  (Track->TrackElementAt(1642, CurrentTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) ||
4033  (Track->TrackElementAt(1643, CurrentTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4034  {
4035  // no need to add in the length of element to CumulativeLength
4036  if(StopRequired)
4037  {
4038  StationFlag = true;
4039  }
4040  }
4041  }
4042  else
4043  {
4044  StationFlag = false;
4045  }
4046  // set NextHalfLength & NextSpeedLimit, but only if current element not buffers or exit continuation - no next element for them
4047  if(((Track->TrackElementAt(354, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(355,
4048  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4049  {
4050  BuffersOrContinuationNowFlag = true;
4051  }
4052  if(!BuffersOrContinuationNowFlag && !BuffersFlag && !StationFlag) // skip if buffers or station flags already set
4053  {
4054  if(Track->TrackElementAt(356, CurrentTrackVectorPosition).TrackType == Points)
4055  {
4056  if((EntryPos == 0) || (EntryPos == 2))
4057  {
4058  if(Track->TrackElementAt(357, CurrentTrackVectorPosition).Attribute == 0)
4059  {
4060  ExitPos = 1;
4061  }
4062  else
4063  {
4064  ExitPos = 3;
4065  }
4066  }
4067  else
4068  {
4069  ExitPos = 0;
4070  }
4071  }
4072  else
4073  {
4074  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4075  }
4076  NextTrackVectorPosition = Track->TrackElementAt(358, CurrentTrackVectorPosition).Conn[ExitPos];
4077  NextEntryPos = Track->TrackElementAt(359, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4078  if(NextTrackVectorPosition > -1)
4079  {
4080  if(Track->TrackElementAt(845, NextTrackVectorPosition).TrackType == Points)
4081  // this test & section added at v0.6
4082  {
4083  if((NextEntryPos == 0) || (NextEntryPos == 2))
4084  {
4085  if(Track->TrackElementAt(846, NextTrackVectorPosition).Attribute == 0)
4086  {
4087  NextElementHalfLength = (Track->TrackElementAt(847, NextTrackVectorPosition).Length01) / 2;
4088  NextSpeedLimit = Track->TrackElementAt(848, NextTrackVectorPosition).SpeedLimit01;
4089  }
4090  else
4091  {
4092  NextElementHalfLength = (Track->TrackElementAt(849, NextTrackVectorPosition).Length23) / 2;
4093  NextSpeedLimit = Track->TrackElementAt(850, NextTrackVectorPosition).SpeedLimit23;
4094  }
4095  }
4096  else if(NextEntryPos == 1)
4097  {
4098  NextElementHalfLength = (Track->TrackElementAt(851, NextTrackVectorPosition).Length01) / 2;
4099  NextSpeedLimit = Track->TrackElementAt(852, NextTrackVectorPosition).SpeedLimit01;
4100  }
4101  else // == 3
4102  {
4103  NextElementHalfLength = (Track->TrackElementAt(853, NextTrackVectorPosition).Length23) / 2;
4104  NextSpeedLimit = Track->TrackElementAt(854, NextTrackVectorPosition).SpeedLimit23;
4105  }
4106  }
4107  else
4108  {
4109  if(NextEntryPos > 1)
4110  {
4111  NextElementHalfLength = (Track->TrackElementAt(360, NextTrackVectorPosition).Length23) / 2;
4112  NextSpeedLimit = Track->TrackElementAt(361, NextTrackVectorPosition).SpeedLimit23;
4113  }
4114  else
4115  {
4116  NextElementHalfLength = (Track->TrackElementAt(362, NextTrackVectorPosition).Length01) / 2;
4117  NextSpeedLimit = Track->TrackElementAt(363, NextTrackVectorPosition).SpeedLimit01;
4118  }
4119  }
4120  }
4121  else
4122  {
4123  throw Exception("Error - Trying to access NextTrackVectorPosition when none present in SetTrainMovementValues");
4124  }
4125  // now check for stops, first cover those where don't want to add in length of next element
4126  // check if next element is a red signal - Attr 0,
4127  // note that this doesn't apply to trains stopped at a red signal since the signal position is
4128  // CurrentTrackVectorPosition not NextTrackVectorPosition
4129  bool StopRequired;
4130  if(Track->TrackElementAt(364, NextTrackVectorPosition).Config[Track->GetNonPointsOppositeLinkPos(NextEntryPos)] == Signal)
4131  {
4132  if(Track->TrackElementAt(365, NextTrackVectorPosition).Attribute == 0)
4133  {
4134  // no need to add in the length of element to CumulativeLength
4135  RedSignalFlag = true;
4136  }
4137  // next element is a red signal
4138  }
4139  // check if current element is a station & next element contains a train - trains will always stop without crashing at a
4140  // station they are due to stop at even if there is a train in front blocking the normal stop position - providing there is
4141  // at least one platform element free
4143  CurrentTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && Track->OtherTrainOnTrack(3, NextTrackVectorPosition, NextEntryPos, TrainID))
4144  {
4145  // no need to add in the length of element to CumulativeLength
4146  if(StopRequired)
4147  {
4148  StationFlag = true;
4149  }
4150  }
4151  // check if next element contains a train & in Signaller mode (always stops for train in front if in signaller mode)
4152  else if((TrainMode == Signaller) && Track->OtherTrainOnTrack(4, NextTrackVectorPosition, NextEntryPos, TrainID))
4153  // (Track->TrackElementAt(651, NextTrackVectorPosition).TrainIDOnElement > -1))
4154  {
4155  // no need to add in the length of element to CumulativeLength
4156  TrainInFrontInSignallerModeFlag = true;
4157  }
4158  // check if next element is a buffer, but if StepForwardFlag true then need to stop before reach the buffers
4159  else if((Track->TrackElementAt(366, NextTrackVectorPosition).TrackType == Buffers) && !StepForwardFlag)
4160  {
4161  // need to add in the length of that element to CumulativeLength
4162  CumulativeLength += Track->TrackElementAt(367, NextTrackVectorPosition).Length01;
4163  BuffersFlag = true;
4164  }
4165  // check if next element is a station stop
4167  NextTrackVectorPosition).ActiveTrackElementName, StopRequired) > -1) && ((Track->TrackElementAt(371,
4168  NextTrackVectorPosition).StationEntryStopLinkPos1 == EntryPos) || (Track->TrackElementAt(372,
4169  NextTrackVectorPosition).StationEntryStopLinkPos2 == EntryPos) || (Track->TrackElementAt(1644,
4170  NextTrackVectorPosition).StationEntryStopLinkPos3 == EntryPos) || (Track->TrackElementAt(1645,
4171  NextTrackVectorPosition).StationEntryStopLinkPos4 == EntryPos)))
4172  { // need to add in the length of that element to CumulativeLength if a stop required
4173  if(StopRequired)
4174  {
4175  StationFlag = true;
4176  CumulativeLength += Track->TrackElementAt(373, NextTrackVectorPosition).Length01;
4177  }
4178  }
4179  }
4180  //now can decide whether need to stop over CumulativeLength
4181  if(RedSignalFlag || BuffersFlag || StationFlag || TrainInFrontInSignallerModeFlag || SignallerStopRequired || StepForwardFlag) // no more loops
4182  {
4183  // have to come to a stop over CumulativeLength
4184  if(CumulativeLength == FrontElementLength)
4185  // will be if StepForwardFlag (if stopped to begin with on zero power then earlier check will intercept it and it won't reach here
4186  // only one length to go before stop so check whether need to accelerate for half length then brake for latter
4187  // half; calc speed at halfway point that corresponds to half braking rate for latter half of track element,
4188  // and if less than EntrySpeed then skip this section (don't need any acceleration)
4189  // if not calc speed at halfway point & if less than above set half speed to this value;
4190  // use constant acceleration in calculating half time point
4191  {
4192  MaxExitSpeed = 0;
4193  double MaxHalfSpeed;
4194  double MaxHalfSpeedAtHalfBraking = 3.6 * sqrt(MaxBrakeRate * FrontElementLength / 2);
4195  // have to halve the element length, & can't be zero or negative so no need to test
4196  // if(MaxHalfSpeedAtHalfBraking > LimitingSpeed) MaxHalfSpeed = LimitingSpeed; else MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4197  if(MaxHalfSpeedAtHalfBraking > FrontElementMaxSpeed)
4198  {
4199  MaxHalfSpeed = FrontElementMaxSpeed;
4200  }
4201  else
4202  {
4203  MaxHalfSpeed = MaxHalfSpeedAtHalfBraking;
4204  }
4205  if(MaxHalfSpeed > (2 * EntrySpeed) && (PowerAtRail > 1)) //PowerAtRail condition added at v2.18.0
4206  // use 2x to prevent kangarooing at last element when had
4207  // been braking smoothly at less that 50% braking rate, 2x should prevent all but extreme cases
4208  {
4209  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4210  0.333334);
4211  bool HalfSpeedLimited = false;
4212  if(MaxHalfSpeed < ExitSpeedHalf)
4213  {
4214  ExitSpeedHalf = MaxHalfSpeed;
4215  HalfSpeedLimited = true;
4216  }
4217  if(PowerAtRail > 1)
4218  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4219  {
4220  // [km/h/3.6 = m/s]
4221  ExitTimeHalf =
4222  EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4223  }
4224  else
4225  {
4226  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4227  }
4228  // the above is the time taken to accelerate to ExitSpeedHalf, so if this is reached before the half
4229  // way point (i.e. HalfSpeedLimited is set) then the time will be too short; change later to equal the
4230  // braking time; not fully accurate but better to be equal than have a short acceleration period followed
4231  // by a long braking period
4232  ExitSpeedFull = 0;
4233  TempBrakeRate = ExitSpeedHalf * ExitSpeedHalf / 2 / 3.6 / 3.6 / EntryHalfLength;
4234  if(TempBrakeRate > MaxBrakeRate)
4235  {
4236  TempBrakeRate = MaxBrakeRate;
4237  }
4238  // shouldn't be but leave in anyway
4239  if(TempBrakeRate > BrakeRate)
4240  {
4241  BrakeRate = TempBrakeRate;
4242  }
4243  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4244  ExitTimeFull = ExitTimeHalf + TDateTime(ExitSpeedHalf / 3.6 / BrakeRate / 86400.0);
4245  if(HalfSpeedLimited)
4246  // this is the change referred to above
4247  {
4248  TDateTime BrakingTime = ExitTimeFull - ExitTimeHalf;
4249  ExitTimeHalf = EntryTime + BrakingTime;
4250  ExitTimeFull = ExitTimeHalf + BrakingTime;
4251  }
4252  OneLengthAccelDecel = true; //used in TrackTrainFloat in InterfaceUnit.cpp to show accelerating for first half move then decelerating
4253  Utilities->CallLogPop(1095);
4254  return;
4255  }
4256  }
4257  // set braking to achieve speed = 0 @ CumulativeLength up to MaxBrakeRate
4258  // calc MaxExitSpeed for element at EntryPosition & set to this or existing val if lower,
4259  // calc th, tf, sh, & sf
4260  TempBrakeRate = EntrySpeed * EntrySpeed / 2 / 3.6 / 3.6 / CumulativeLength;
4261  if(TempBrakeRate > MaxBrakeRate)
4262  {
4263  TempBrakeRate = MaxBrakeRate;
4264  }
4265  if(TempBrakeRate > BrakeRate)
4266  {
4267  BrakeRate = TempBrakeRate;
4268  }
4269  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4270  if(SignallerStopRequired)
4271  // set BrakeRate to max of its calculated value or SignallerStopBrakeRate
4272  {
4274  {
4276  // this prevents the brakerate from reducing for a signaller stop
4277  // regardless of other conditions that may change as progress round the loop
4278  }
4279  }
4281  // prevents BrakeRate dropping below SignallerStopBrakeRate once it's been set whether or not SignallerStopRequired set
4282  // SignallerStopRequired may not be set if a red signal found in a later calc, & brakerate may then drop
4283  {
4285  }
4286  int TempMaxExitSpeed;
4287  // calc current value & if less than MaxExitSpeed set that to this
4288  MaxExitSpeedAtHalfBrakingSquared = 3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength);
4289  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4290  {
4291  MaxExitSpeedAtHalfBraking = 0;
4292  }
4293  else
4294  {
4295  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4296  }
4297  // if(MaxExitSpeedAtHalfBraking > LimitingSpeed) MaxExitSpeed = LimitingSpeed; else MaxExitSpeed = MaxExitSpeedAtHalfBraking;
4298  // I think the above was dropped because it could cause MaxExitSpeed to increase (MaxExitSpeed is an external variable retained between loops)
4299  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4300  {
4301  TempMaxExitSpeed = FrontElementMaxSpeed;
4302  }
4303  else
4304  {
4305  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4306  }
4307  if(TempMaxExitSpeed < MaxExitSpeed)
4308  {
4309  MaxExitSpeed = TempMaxExitSpeed;
4310  }
4311  // here have EntrySpeed & MaxExitSpeed (for the next element), BrakeRate (to bring speed to zero over
4312  // Cumulativelength, and Cumulativelength
4313 
4314  if((EntrySpeed > MaxExitSpeed) || SignallerStopRequired || (SignallerStopBrakeRate > 0.01)) // need to brake
4315  {
4316  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4317  if(ExitSpeedHalfSquared < 10)
4318  {
4319  ExitSpeedHalf = 0;
4320  }
4321  else
4322  {
4323  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4324  }
4325  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4326  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4327  if(ExitSpeedFullSquared < 10)
4328  {
4329  ExitSpeedFull = 0;
4330  }
4331  else
4332  {
4333  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4334  }
4335  if((StationFlag) && (CumulativeLength == FrontElementLength))
4336  {
4337  ExitSpeedFull = 0;
4338  // force a stop for station (not for buffers or red signal)
4339  }
4340  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4341  }
4342  // new condition at v2.4.0
4343  else if(PowerAtRail < 1)
4344  // use EntrySpeed, CumulativeLength & BrakeRate to calculate the half and full exit times and speeds for next element
4345  // avoid using AValue in denominator or have excessively long times
4346  {
4347  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * BrakeRate * FrontElementLength);
4348  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4349  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / (3.6 * BrakeRate * 86400.0));
4350 
4351  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * FrontElementLength);
4352  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4353  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / (3.6 * BrakeRate * 86400.0));
4354  }
4355  else // e.g. moving towards a signal or station after a speed limit, so can accelerate unless no power
4356  // without the power need above condition or have hours of delay times, above added at v2.4.0
4357  {
4358  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4359  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/(3.6^3))^0.333334;
4360  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph, S = distance & T = time, note that km/h/3.6 = m/s
4361  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4362  BrakeRate = 0;
4363  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)),
4364  0.333334);
4365  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4366  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4368  // can accelerate continually over the half length
4369  {
4370  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4371  / 86400.0);
4373  // can accelerate continually over the full length
4374  // i.e both (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4375  {
4376  ExitTimeFull =
4377  EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue) / 86400.0);
4378  }
4379  else // (ExitSpeedHalf <= MaxExitSpeed) but (ExitSpeedFull > MaxExitSpeed)
4380  // accelerate to MaxExitSpeed then reamin at this speed for rest of element
4381  {
4382  // added at v0.6 as a safeguard
4383  if(MaxExitSpeed < EntrySpeed)
4384  {
4386  }
4387  // to prevent DeltaExitTimeToMaxInSecs being negative
4388  if(MaxExitSpeed < 1)
4389  {
4390  MaxExitSpeed = 1;
4391  }
4392  // to prevent divide by zero error
4393  // below as was
4395  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4396  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4397  (1.5 * AValue * AValue);
4398  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4399  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4400  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4401  }
4402  }
4403  else // ExitSpeedHalf > MaxExitSpeed, so ExitSpeedFull must also be > MaxExitSpeed
4404  // accelerate over first half to MaxExitSpeed then remain at this speed for rest of the first and
4405  // second halves of the element
4406  {
4407  // added at v0.6 as a safeguard
4408  if(MaxExitSpeed < EntrySpeed)
4409  {
4411  }
4412  // to prevent DeltaExitTimeToMaxInSecs being negative
4413  if(MaxExitSpeed < 1)
4414  {
4415  MaxExitSpeed = 1; // to prevent divide by zero error
4416  }
4417  // below as was
4419  double DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4420  double DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4421  (1.5 * AValue * AValue);
4422  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax;
4423  // remaining distance to half length
4424  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4425  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4427  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4428  }
4429  }
4430  Utilities->CallLogPop(706);
4431  return;
4432  }
4433  else
4434  {
4435  if(!BuffersOrContinuationNowFlag)
4436  {
4437  if(NextSpeedLimit < LimitingSpeed)
4438  {
4439  LimitingSpeed = NextSpeedLimit;
4440  }
4441  }
4442  // calc max exit speed at half braking to ensure don't accelerate past it (if acceleration is required)
4443  int TempMaxExitSpeed;
4444  // calc current value & if less than MaxExitSpeed set that to this
4445  // Note that LimitingSpeed is the max value at the end of CumulativeLength, so MaxExitSpeedAtHalfBrakingSquared will be larger
4446  MaxExitSpeedAtHalfBrakingSquared = (LimitingSpeed * LimitingSpeed) + (3.6 * 3.6 * MaxBrakeRate * (CumulativeLength - FrontElementLength));
4447  if(MaxExitSpeedAtHalfBrakingSquared < 10)
4448  {
4449  MaxExitSpeedAtHalfBraking = 0;
4450  }
4451  else
4452  {
4453  MaxExitSpeedAtHalfBraking = sqrt(MaxExitSpeedAtHalfBrakingSquared);
4454  }
4455  if(MaxExitSpeedAtHalfBraking > FrontElementMaxSpeed)
4456  {
4457  TempMaxExitSpeed = FrontElementMaxSpeed;
4458  }
4459  else
4460  {
4461  TempMaxExitSpeed = MaxExitSpeedAtHalfBraking;
4462  }
4463  if(TempMaxExitSpeed < MaxExitSpeed)
4464  {
4465  MaxExitSpeed = TempMaxExitSpeed;
4466  }
4467  // MaxExitSpeed is an external variable & this can reduce its value
4468  if(EntrySpeed > LimitingSpeed)
4469  // note that LimitingSpeed is more restrictive than MaxExitSpeed
4470  // calc TempBrakeRate & set BrakeRate to this or keep existing val if higher
4471  {
4472  TempBrakeRate = ((EntrySpeed * EntrySpeed) - (LimitingSpeed * LimitingSpeed)) / 3.6 / 3.6 / 2 / CumulativeLength;
4473  if(TempBrakeRate > MaxBrakeRate)
4474  {
4475  TempBrakeRate = MaxBrakeRate;
4476  }
4477  // shouldn't be for speedlimits since all known in advance, but include anyway
4478  if(TempBrakeRate > BrakeRate)
4479  {
4480  BrakeRate = TempBrakeRate;
4481  }
4482  // BrakeRate may already have been set in an earlier loop so don't want to reduce it
4483  }
4484  }
4485  if(!BuffersOrContinuationNowFlag)
4486  {
4487  CurrentTrackVectorPosition = NextTrackVectorPosition;
4488  EntryPos = NextEntryPos;
4489  CurrentElementHalfLength = NextElementHalfLength;
4490  if(Track->TrackElementAt(378, NextTrackVectorPosition).TrackType == Continuation)
4491  {
4492  ContinuationNextFlag = true;
4493  }
4494  }
4495  }
4496  while(((CumulativeLength - FrontElementLength) < DistanceAtHalfBraking) && ((!BuffersOrContinuationNowFlag && !ContinuationNextFlag) ||
4498  // check from the end of the front element, if include the front element and could brake during it, then will skip further loops
4499  // & miss a stop requirement just beyond the front element. happened in Richard Standing's railway where a new service introduced
4500  // on a 100m length, with 20m length after & then a red signal - train accelerated over the 100m then caused a SPAD as too short a
4501  // stopping distance after it.
4502 
4503  //(!BuffersOrContinuationNowFlag && !ContinuationNextFlag) true when no continuation on either the next element and next but one element.
4504  //There won't be a buffer on the next element or would have caught earlier, just using this flag for convenience.
4505 
4506  // If SignallerStoppingFlag then don't exit loop because of an imminent continuation, because continuation
4507  // not immediately in front (if it is then LeadElement will be the continuation & SignallerStoppingFlag will be reset in UpdateTrain()),
4508  // need to at least give a chance to stop on signaller command, if keep moving until continuation is immediately in front then will
4509  // exit loop & that is OK as don't want to stop so close to a continuation, if that happens it means that the command to stop was given
4510  // too late
4511 
4512  // set final braking or acc'n speed & time values
4513  if(BrakeRate > 0.01)
4514  {
4515  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4516  if(ExitSpeedHalfSquared < 10)
4517  {
4518  ExitSpeedHalf = 0;
4519  }
4520  else
4521  {
4522  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4523  }
4524  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4525  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4526  if(ExitSpeedFullSquared < 10)
4527  {
4528  ExitSpeedFull = 0;
4529  }
4530  else
4531  {
4532  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4533  }
4534  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4535  }
4536  else
4537  {
4538  // for accelerating the energy input rate (PowerAtRail) is constant so the following formuli are used:-
4539  // (1) V^2/(3.6^2) - U^2/(3.6^2) = A^2T; (2) V = 3.6 * ((1.5*S*A^2) + U^3/ (3.6)^3)^0.333334;
4540  // where A is a constant (2*PowerAtRail/Mass)^0.5; V = final speed in kph, U = initial speed in kph , S = distance in m & T = time
4541  // This is a bit unrealistic during the early acceleration phase as it will be too rapid, but shouldn't affect the running unduly
4542 
4543  BrakeRate = 0;
4544  ExitSpeedHalf = 3.6 * Power(((1.5 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4545  ExitSpeedFull = 3.6 * Power(((3 * EntryHalfLength * AValue * AValue) + (EntrySpeed * EntrySpeed * EntrySpeed / 3.6 / 3.6 / 3.6)), 0.333334);
4546  // above valid for ExitSpeedHalf & Full <= MaxExitSpeed
4548  {
4549  if(PowerAtRail > 1)
4550  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4551  {
4552  // [km/h/3.6 = m/s]
4553  ExitTimeHalf = EntryTime + TDateTime(((ExitSpeedHalf * ExitSpeedHalf) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4554  / 86400.0);
4555  }
4556  else
4557  {
4558  ExitTimeHalf = EntryTime + TDateTime(EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4559  }
4561  // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull <= MaxExitSpeed)
4562  {
4563  if(PowerAtRail > 1)
4564  // added at v2.4.0 in case reach here with failed train, when can't use AValue in denominator as close zero
4565  {
4566  // [km/h/3.6 = m/s]
4567  ExitTimeFull = EntryTime + TDateTime(((ExitSpeedFull * ExitSpeedFull) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue)
4568  / 86400.0);
4569  }
4570  else
4571  {
4572  ExitTimeFull = EntryTime + TDateTime(2 * EntryHalfLength * 3.6 / EntrySpeed / 86400.0);
4573  }
4574  }
4575  else // (ExitSpeedHalf <= MaxExitSpeed) & (ExitSpeedFull > MaxExitSpeed)
4576  {
4577  // added at v0.6 as a safeguard
4578  if(MaxExitSpeed < EntrySpeed)
4579  {
4581  }
4582  // to prevent DeltaExitTimeToMaxInSecs being negative
4583  if(MaxExitSpeed < 1)
4584  {
4585  MaxExitSpeed = 1; // to prevent divide by zero error
4586  }
4587  // below as was
4589  double DeltaExitTimeToMaxInSecs;
4590  double DistanceToMax;
4591  if(PowerAtRail > 1) // added at v2.4.0
4592  {
4593  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4594  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4595  (1.5 * AValue * AValue);
4596  }
4597  else // shouldn't ever get here given that ExitSpeedFull > ExitSpeedHalf
4598  {
4599  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4600  // these not really accurate but will be good enough
4601  DistanceToMax = EntryHalfLength;
4602  }
4603  double RemainingDistance = double(FrontElementLength) - DistanceToMax;
4604  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4605  ExitTimeFull = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4606  }
4607  }
4608  else // ExitSpeedHalf > MaxExitSpeed, ExitSpeedFull must be > MaxExitSpeed
4609  {
4610  // added at v0.6 as a safeguard
4611  if(MaxExitSpeed < EntrySpeed)
4612  {
4614  }
4615  // to prevent DeltaExitTimeToMaxInSecs being negative
4616  if(MaxExitSpeed < 1)
4617  {
4618  MaxExitSpeed = 1; // to prevent divide by zero error
4619  }
4620  // below as was
4622  double DeltaExitTimeToMaxInSecs;
4623  double DistanceToMax;
4624  if(PowerAtRail > 1) // added at v2.4.0
4625  {
4626  DeltaExitTimeToMaxInSecs = ((MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / (AValue * AValue);
4627  DistanceToMax = ((MaxExitSpeed * MaxExitSpeed * MaxExitSpeed) - (EntrySpeed * EntrySpeed * EntrySpeed)) / 3.6 / 3.6 / 3.6 /
4628  (1.5 * AValue * AValue);
4629  }
4630  else
4631  {
4632  DeltaExitTimeToMaxInSecs = 2 * EntryHalfLength * 3.6 / EntrySpeed;
4633  // these not really accurate but will be good enough
4634  DistanceToMax = EntryHalfLength / 2;
4635  }
4636  double RemainingDistance = double(FrontElementLength / 2) - DistanceToMax; // remaining distance to half length
4637  double DeltaRemainingTimeInSecs = 3.6 * RemainingDistance / MaxExitSpeed;
4638  ExitTimeHalf = EntryTime + TDateTime((DeltaExitTimeToMaxInSecs + DeltaRemainingTimeInSecs) / 86400.0);
4640  ExitTimeFull = ExitTimeHalf + TDateTime(3.6 * EntryHalfLength / MaxExitSpeed / 86400.0);
4641  }
4642  }
4643  }
4644 
4645  else // SPADFlag set
4646  {
4648  ExitSpeedHalfSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 2 * BrakeRate * EntryHalfLength);
4649  if(ExitSpeedHalfSquared < 10)
4650  {
4651  ExitSpeedHalf = 0;
4652  }
4653  else
4654  {
4655  ExitSpeedHalf = sqrt(ExitSpeedHalfSquared);
4656  }
4657  ExitTimeHalf = EntryTime + TDateTime((EntrySpeed - ExitSpeedHalf) / 3.6 / BrakeRate / 86400.0);
4658  ExitSpeedFullSquared = (EntrySpeed * EntrySpeed) - (3.6 * 3.6 * 4 * BrakeRate * EntryHalfLength);
4659  if(ExitSpeedFullSquared < 10)
4660  {
4661  ExitSpeedFull = 0;
4662  }
4663  else
4664  {
4665  ExitSpeedFull = sqrt(ExitSpeedFullSquared);
4666  }
4667  ExitTimeFull = EntryTime + TDateTime((EntrySpeed - ExitSpeedFull) / 3.6 / BrakeRate / 86400.0);
4668 
4669  // check if the exit speed is < 80% of the stopping speed for the next element, and if so stop at end of this element
4670  // this is because would stop short of end of next element (in reality the time to reach the end of the next element
4671  // would be too short (could be so short as to make the train jump) as time is calculated purely on speed & brake rate);
4672  // 80% is used as the brake rate might be set to come to a halt at the end of the next element in which case the speed
4673  // will be the stopping speed.
4674  if(ExitSpeedFull > 0)
4675  {
4676  if(Track->TrackElementAt(746, CurrentTrackVectorPosition).TrackType == Points)
4677  {
4678  if((EntryPos == 0) || (EntryPos == 2))
4679  {
4680  if(Track->TrackElementAt(747, CurrentTrackVectorPosition).Attribute == 0)
4681  {
4682  ExitPos = 1;
4683  }
4684  else
4685  {
4686  ExitPos = 3;
4687  }
4688  }
4689  else
4690  {
4691  ExitPos = 0;
4692  }
4693  }
4694  else
4695  {
4696  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4697  }
4698  NextTrackVectorPosition = Track->TrackElementAt(748, CurrentTrackVectorPosition).Conn[ExitPos];
4699  NextEntryPos = Track->TrackElementAt(749, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4700  if(NextTrackVectorPosition > -1) // not a continuation or buffer
4701  {
4702  int NextElementLength;
4703  if(NextEntryPos > 1)
4704  {
4705  NextElementLength = (Track->TrackElementAt(750, NextTrackVectorPosition).Length23);
4706  }
4707  else
4708  {
4709  NextElementLength = (Track->TrackElementAt(751, NextTrackVectorPosition).Length01);
4710  }
4711  double NextStoppingSpeed = sqrt(3.6 * 3.6 * 2 * BrakeRate * NextElementLength);
4712  if(ExitSpeedFull < (0.8 * NextStoppingSpeed))
4713  {
4714  ExitSpeedFull = 0;
4715  }
4716  }
4717  }
4718  }
4719  // allow all values to be set normally in case need to brake, then test for zero power & need to coast
4720  if(PowerAtRail < 1) // new at v2.4.0 note that km/h/3.6 = m/s
4721  {
4722  // bring to a stop in 20 elements at 100km/h & assume each 100m long for calculating exit times but if on a continuation maintain speed <--NO,
4723  //change to BrakeRate = CoastingBrakeRate = 0.03 and calc times etc as normal - because of Albie Vowles' error report of 231223 where noticed
4724  //that failed train treated track lengths of > 2km as 100m so very noticeable. Keep going for exiting at continuation.
4725 
4726  //Coasting deceleration rate from paper 'Real-time train motion parameter estimation using an Unscented Kalman Filter' at
4727  //'https://www.sciencedirect.com/science/article/pii/S0968090X22002212'. In particular Fig 6 in section 4.3 shows coasting from 400sec to
4728  //1000sec corresponds to speed drop from 140km/h to 80km/h, i.e. 60km/h in 600sec, equivalent to 0.02777m/s/s deceleration, so use 0.03m/s/s.
4729 
4730  if(LeadElement > -1)
4731  {
4733  // don't stop on a continuation either entering or leaving
4734  {
4737  MaxExitSpeed = LimitingSpeed;
4738  BrakeRate = 0;
4739  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
4740  // assume length is 50m for ease of calc
4741  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
4742  Utilities->CallLogPop(2126);
4743  return;
4744  }
4745  }
4746  else if(MidElement > -1)
4747  {
4749  {
4752  MaxExitSpeed = LimitingSpeed;
4753  BrakeRate = 0;
4754  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
4755  // assume length is 50m for ease of calc
4756  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
4757  Utilities->CallLogPop(2127);
4758  return;
4759  }
4760  }
4761  else if(LagElement > -1)
4762  {
4764  {
4767  MaxExitSpeed = LimitingSpeed;
4768  BrakeRate = 0;
4769  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed) / 86400.0));
4770  // assume length is 50m for ease of calc
4771  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf) / 86400.0));
4772  Utilities->CallLogPop(2128);
4773  return;
4774  }
4775  }
4776 /* dropped at v2.18.1 in favour of CoastingBrakeRate which = 0.03m/s/s = see above explanation
4777  if(EntrySpeed > 7.5) // keep going for at least another element
4778  {
4779  ExitSpeedHalf = EntrySpeed - 2.5;
4780  ExitSpeedFull = EntrySpeed - 5;
4781  MaxExitSpeed = LimitingSpeed;
4782  BrakeRate = 0;
4783  ExitTimeHalf = EntryTime + TDateTime((50 * 3.6 / (EntrySpeed - 1.25) / 86400.0));
4784  // assume length is 50m for ease of calc
4785  ExitTimeFull = ExitTimeHalf + TDateTime((50 * 3.6 / (ExitSpeedHalf - 1.25) / 86400.0));
4786  Utilities->CallLogPop(2129);
4787  return;
4788  }
4789  else // stop immediately, don't enter next element, floating label had displayed last exit speed full on 2nd half move so with zero when fully on element
4790  {
4791  // will appear to have slowed at steady rate
4792  ExitSpeedHalf = 0;
4793  ExitSpeedFull = 0;
4794  MaxExitSpeed = LimitingSpeed;
4795  BrakeRate = 0;
4796  ExitTimeHalf = EntryTime + TDateTime(1/24); //set this high in case used later though unlikely
4797  ExitTimeFull = EntryTime + TDateTime(1/23); //set about 2.5 mins later than half time
4798  StoppedWithoutPower = true;
4799  Utilities->CallLogPop(2130);
4800  return;
4801  }
4802 */
4803  }
4804  // TempBrakeRate=MinSingle; TempBrakeRate=MaxSingle; TempBrakeRate=MinDouble; TempBrakeRate=MaxDouble;//included to stop warnings from unused declarations in math.hpp
4805  // TempBrakeRate=MinExtended; TempBrakeRate=MaxExtended; TempBrakeRate=MinComp; TempBrakeRate=MaxComp;//included to stop warnings from unused declarations in math.hpp
4806  Utilities->CallLogPop(707);
4807 }
4808 // ---------------------------------------------------------------------------
4809 /*
4810  bool TTrain::IsTerminalStation(int TrackVectorPosition, int EntryPos)
4811  {
4812  int NextExitPos;
4813  TTrackElement NextElement = Track->TrackElementAt(379, TrackVectorPosition), TempElement;
4814  if(TimetableVector.empty()) return false;
4815  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
4816  while((NextElement.ActiveTrackElementName == TimetableVector.begin()->LocationName) && (NextElement.TrackType != Buffers))
4817  {
4818  //check for points & follow attribute, but don't worry about a derail as that dealt with elsewhere
4819  if((NextElement.TrackType != Points) || ((EntryPos != 0) && (EntryPos != 2)))
4820  {
4821  NextExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4822  }
4823  else if((NextElement.TrackType == Points) && ((EntryPos == 0) || (EntryPos == 2)))
4824  {
4825  if(NextElement.Attribute == 0) NextExitPos = 1; else NextExitPos = 3;
4826  }
4827  TempElement = Track->TrackElementAt(380, NextElement.Conn[NextExitPos]);//need temp as NextElement used in next step
4828  NextElement = TempElement;
4829  }
4830  if(NextElement.ActiveTrackElementName != TimetableVector.begin()->LocationName) return false;
4831  if(NextElement.TrackType == Buffers) return true;
4832  return false;//shouldn't reach here but include to prevent compiler return warning
4833  }
4834 */
4835 // ---------------------------------------------------------------------------
4836 
4837 int TTrain::NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
4838 /*
4839  returns the number by which the train ActionVectorEntryPtr needs
4840  to be incremented to point to the location arrival entry or passtime entry before a change of direction. Used to display missed
4841  actions when a stop or pass location has been reached before other timetabled events have been carried out. If can't find it, or Name
4842  is "", -1 is returned. A change of direction is the limit of the search because a train may not stop at a location on the way out
4843  but stop on way back, and in these circumstances no actions have been missed. Stop indicates whether the train will stop at (true)
4844  or pass (false) the location.
4845 */{
4846  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NameInTimetableBeforeCDT," + Name + "," + HeadCode);
4847  Stop = false;
4848  if(TimetableFinished || (Name == ""))
4849  {
4850  Utilities->CallLogPop(957);
4851  return(-1);
4852  }
4854 /*added the following check at v2.11.1 because of a fault found in Kevin Smith's railway (notified 02/01/22). CH01 started at
4855 Chester behind the stop position, but when it departed and this function was called it found Chester again with no cdt
4856 before it (it went round the Liverpool Loop), so it stopped again when it reached the stop position and reported all intermediate stops as having been
4857 missed. This check is for the train just having departed from the station in question, and if it has then any further stations
4858 with the same name are ignored - i.e. it stops a train from stopping at the same station twice in succession.
4859 */
4860  if(Ptr > &TrainDataEntryPtr->ActionVector.at(0))
4861  {
4862  Ptr--;
4863  if((Ptr->DepartureTime > TDateTime(-1)) && (Ptr->LocationName == Name))
4864  {
4865  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
4866  {
4867  Utilities->CallLogPop(2444);
4868  return(-1);
4869  }
4870  }
4871  }
4872  // start looking from current pointer position
4873  for(TActionVectorEntry *Ptr = ActionVectorEntryPtr; Ptr < &TrainDataEntryPtr->ActionVector.back(); Ptr++)
4874  {
4875  if((Ptr->Command == "cdt") || (Ptr->FormatType == Repeat))
4876  {
4877  break;
4878  }
4879  if((Ptr->ArrivalTime > TDateTime(-1)) && (Ptr->LocationName == Name))
4880  {
4881  if((Ptr->FormatType == TimeLoc) || (Ptr->FormatType == TimeTimeLoc))
4882  {
4883  Stop = true;
4884  Utilities->CallLogPop(960);
4885  return (Ptr - ActionVectorEntryPtr);
4886  }
4887  }
4888  if((Ptr->EventTime > TDateTime(-1)) && (Ptr->LocationName == Name) && (Ptr->Command == "pas"))
4889  {
4890  Utilities->CallLogPop(1517);
4891  return (Ptr - ActionVectorEntryPtr);
4892  }
4893  }
4894  Utilities->CallLogPop(959);
4895  return(-1); // not found a valid entry
4896 }
4897 
4898 // ---------------------------------------------------------------------------
4899 
4901 /* Checks forward from train LeadElement, following leading point attributes but ignoring trailing point attributes,
4902  until finds either a train or a signal/buffers/continuation/loop. If finds a train returns false, else returns true.
4903  Ignores the call-on signal.
4904 */{
4905  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ClearToNextSignal" + "," + HeadCode);
4906  int ReturnVal = 0;
4907  int ElementCount = 0;
4908 /* dropped at v2.12.0 as takes up a great deal of time unnecessarily - substitute 1000 elements instead and return true (very unlikely to need to search this far [10km at min length])
4909  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
4910  {
4911  Track->TrackElementAt(1031, x).TempTrackMarker01 = false;
4912  Track->TrackElementAt(1032, x).TempTrackMarker23 = false;
4913  }
4914 */
4915  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition;
4916  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos;
4917 
4918  while(true)
4919  {
4920  if((Track->TrackElementAt(382, CurrentTrackVectorPosition).TrainIDOnElement > -1) && (Track->TrackElementAt(383,
4921  CurrentTrackVectorPosition).TrainIDOnElement != TrainID))
4922  {
4923  ReturnVal = 1;
4924  break;
4925  }
4926  if(((Track->TrackElementAt(384, CurrentTrackVectorPosition).TrackType == Buffers) || (Track->TrackElementAt(385,
4927  CurrentTrackVectorPosition).TrackType == Continuation)) && (EntryPos == 1))
4928  {
4929  ReturnVal = 2;
4930  break;
4931  }
4932  if((EntryPos < 2) && (Track->TrackElementAt(386, CurrentTrackVectorPosition).Config[1 - EntryPos] == Signal) && !Track->TrackElementAt(529,
4933  CurrentTrackVectorPosition).CallingOnSet && (LeadElement != CurrentTrackVectorPosition)) // CallingOnSet true when position lights lit,
4934  {//added LeadElement condition at v2.18.0 as train may be on the callon signal after CallOnSet false & don't want to return true for that
4935  ReturnVal = 3;
4936  break;
4937  }
4938 /* not needed at and after v2.12.0, see above
4939  if((Track->TrackElementAt(387, CurrentTrackVectorPosition).TrackType == Bridge) || (Track->TrackElementAt(388, CurrentTrackVectorPosition).TrackType == Crossover))
4940  {
4941  if((EntryPos < 2) && (Track->TrackElementAt(523, CurrentTrackVectorPosition).TempTrackMarker01))
4942  // must be a loop - reached same point as examined earlier
4943  {
4944  ReturnVal = 4;
4945  break;
4946  }
4947  else if((EntryPos > 1) && (Track->TrackElementAt(524, CurrentTrackVectorPosition).TempTrackMarker23))
4948  {
4949  ReturnVal = 4;
4950  break;
4951  }
4952  }
4953  else
4954  {
4955  if((Track->TrackElementAt(525, CurrentTrackVectorPosition).TempTrackMarker01) || (Track->TrackElementAt(526, CurrentTrackVectorPosition).TempTrackMarker23))
4956  {
4957  ReturnVal = 4;
4958  break;
4959  }
4960  }
4961  if(EntryPos < 2)
4962  {
4963  Track->TrackElementAt(389, CurrentTrackVectorPosition).TempTrackMarker01 = true;
4964  }
4965  else
4966  {
4967  Track->TrackElementAt(527, CurrentTrackVectorPosition).TempTrackMarker23 = true;
4968  }
4969 */
4970 
4971  if(Track->TrackElementAt(390, CurrentTrackVectorPosition).TrackType == Points)
4972  {
4973  if((EntryPos == 0) || (EntryPos == 2))
4974  {
4975  if(Track->TrackElementAt(391, CurrentTrackVectorPosition).Attribute == 0)
4976  {
4977  ExitPos = 1;
4978  }
4979  else
4980  {
4981  ExitPos = 3;
4982  }
4983  }
4984  else
4985  {
4986  ExitPos = 0;
4987  }
4988  }
4989  else
4990  {
4991  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
4992  }
4993  NextTrackVectorPosition = Track->TrackElementAt(392, CurrentTrackVectorPosition).Conn[ExitPos];
4994  NextEntryPos = Track->TrackElementAt(393, CurrentTrackVectorPosition).ConnLinkPos[ExitPos];
4995  CurrentTrackVectorPosition = NextTrackVectorPosition;
4996  EntryPos = NextEntryPos;
4997  ElementCount++;
4998  if(ElementCount > 1000)
4999  {
5000  ReturnVal = 4;
5001  break;
5002  }
5003  }
5004  if(ReturnVal == 1)
5005  {
5006  Utilities->CallLogPop(708);
5007  return(false);
5008  }
5009  if(ReturnVal == 2)
5010  {
5011  Utilities->CallLogPop(709);
5012  return(true);
5013  }
5014  if(ReturnVal == 3)
5015  {
5016  Utilities->CallLogPop(946);
5017  return(true);
5018  }
5019  if(ReturnVal == 4)
5020  {
5021  Utilities->CallLogPop(947);
5022  return(true);
5023  }
5024  else
5025  {
5026  throw Exception("Error - failed to set ReturnVal in ClearToNextSignal()");
5027  }
5028 }
5029 
5030 // ---------------------------------------------------------------------------
5031 
5033 /*
5034  Check whether calling-on conditions met - a) approaching train has stopped at a signal but not at a location;
5035  b) if there is a facing train at the station, it is being held appropriately (must be awaiting a join (Fjo or jbo) or a
5036  change of direction (cdt), remaining here (Frh), or under signaller control);
5037  c) at least 1 platform available for the approaching train; d) points (if any) set for direct route into platform;
5038  e) approaching train is to stop at station; f) no more facing signals between train and platform; g) [dropped g]
5039  h) train in front preventing route being set far enough to release stop signal; i) train in front not exiting at continuation; j) signal must be within 4km of
5040  the stop platform; k) [dropped (k), now can set a reoute or part route into platform so can set points more easily.] l) no existing route conflicts with the route into the platform,
5041  m) not failed or stopped without power
5042  If all OK & route or part route not already set then set an unrestricted route into the station (just to the first platform), to prevent point changing or other route conflicts - if a partial route set than can still
5043  change points outside the route or have a route conflict if another route is set.
5044 */{
5045  if(Track->RouteFlashFlag || TrainFailed || StoppedWithoutPower) //failed & no power conditions added at v2.10.0
5046  {
5047  return(false); // don't want to create a new route from the stop signal if one is already in construction & can't call on if failed or no power
5048  }
5049  // some of the callingon route elements may be involved
5050  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CallingOnAllowed" + "," + HeadCode);
5051  bool PlatformFoundFlag = false, StopRequired = false, SkipRouteCheck = false, RouteOrPartRouteSet = false; // last added at v1.2.0
5052  int CurrentTrackVectorPosition = LeadElement, NextTrackVectorPosition, ElementNumber = 0, Distance = 0;
5053  int RouteStartPosition;
5054  // this is the track vector position of the start element for the new unrestricted route - one past the stop signal
5055  int PlatformPosition;
5056  // the track vector position of the first stop platfrom
5057  int EntryPos = LeadEntryPos, ExitPos, NextEntryPos, RouteID;
5058  // not used here
5059  AnsiString LeadStationName = Track->TrackElementAt(395, LeadElement).ActiveTrackElementName; // still OK even if ""
5060  int LeadElementDistance = Track->TrackElementAt(1017, LeadElement).Length01; //added after 2.7.0 as don't want to add this to overall distance since train has already covered this distance
5061  // use Length01, may be wrong for points/crossovers/bridges but unlikely to occur in practice
5062  // must be stopped at a signal but not at a location & still in timetable (a)
5064  // no need to check for SignallerStopped as this function only called in Timetable mode
5065  {
5066  Utilities->CallLogPop(711);
5067  return(false);
5068  }
5069  while(true)
5070  {
5071  TTrackElement &CurrentTrackElement = Track->TrackElementAt(396, CurrentTrackVectorPosition);
5072  // don't look further than 4km ahead (j)
5073  if(Distance > (4000 + LeadElementDistance))
5074  {
5075  Utilities->CallLogPop(967);
5076  return(false);
5077  }
5078  // if find another train on an element in front, before find a valid platform, return false (c)
5079  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && !PlatformFoundFlag)
5080  {
5081  Utilities->CallLogPop(713);
5082  return(false);
5083  }
5084  // if find another train in front when there is a valid platform (keep searching after find a platform as train may still
5085  // be facing later on)
5086  if((CurrentTrackElement.TrainIDOnElement > -1) && (CurrentTrackElement.TrainIDOnElement != TrainID) && PlatformFoundFlag)
5087  {
5088  // get LeadElement, if -1 return (could be exiting at continuation) (i)
5089  TTrain OtherTrain = TrainController->TrainVectorAtIdent(12, CurrentTrackElement.TrainIDOnElement);
5090  if(OtherTrain.LeadElement == -1)
5091  {
5092  Utilities->CallLogPop(714);
5093  return(false);
5094  }
5095  // if a facing train then make sure it is awaiting a join (Fjo or jbo) or a change of direction (cdt), or remaining here (Frh) (b)
5096  if(OtherTrain.LeadElement == CurrentTrackVectorPosition)
5097  {
5098  AnsiString OtherCommand = OtherTrain.ActionVectorEntryPtr->Command;
5099  if((OtherCommand == "Fjo") || (OtherCommand == "jbo") || (OtherCommand == "cdt") || (OtherCommand == "Frh") ||
5100  (OtherTrain.TrainMode == Signaller))
5101  {
5102  break;
5103  }
5104  else
5105  {
5106  Utilities->CallLogPop(955);
5107  return(false);
5108  }
5109  }
5110  else // (h)
5111  {
5112  break;
5113  }
5114  }
5115  // if reach buffers or exit continuation return false (can set route)
5116  if(((CurrentTrackElement.TrackType == Buffers) || (CurrentTrackElement.TrackType == Continuation)) && (EntryPos == 1))
5117  {
5118  Utilities->CallLogPop(716);
5119  return(false);
5120  }
5121  // if reach forward signal (other than the one the train is waiting at) return false (can set route) (f)
5122  if((EntryPos < 2) && (CurrentTrackElement.Config[1 - EntryPos] == Signal) && (CurrentTrackVectorPosition != Track->TrackElementAt(404,
5124  {
5125  Utilities->CallLogPop(717);
5126  return(false);
5127  }
5128  // if reach a location that isn't in timetable return false - drop this as still can't set a route
5129 /*
5130  if((Track->TrackElementAt(405, CurrentTrackVectorPosition).ActiveTrackElementName != "") && (Track->TrackElementAt(406, CurrentTrackVectorPosition).ActiveTrackElementName != LeadStationName) &&
5131  (NameInTimetableBeforeCDT(14, Track->TrackElementAt(407, CurrentTrackVectorPosition).ActiveTrackElementName) == -1))
5132  {
5133  Utilities->CallLogPop(718);
5134  return false;
5135  }
5136 */
5137  // if reach a location that is in timetable set PlatformFoundFlag (but not if position is points set to diverge) (e)
5138  if((CurrentTrackElement.ActiveTrackElementName != "") && (CurrentTrackElement.ActiveTrackElementName != LeadStationName) &&
5139  (NameInTimetableBeforeCDT(15, CurrentTrackElement.ActiveTrackElementName, StopRequired) > -1))
5140  {
5141  if(StopRequired)
5142  {
5143  if((CurrentTrackElement.TrackType != Points) || ((CurrentTrackElement.TrackType == Points) && (CurrentTrackElement.Attribute == 0)))
5144  {
5145  if(!PlatformFoundFlag)
5146  {
5147  PlatformPosition = CurrentTrackVectorPosition;
5148  }
5149  // ensure this only set once at first valid platform position - the unrestricted route will end here
5150  PlatformFoundFlag = true;
5151  }
5152  }
5153  }
5154  // Drop this below - was to prevent call-on if front train had left the station. Criterion now is not that front
5155  // train has to be at station but that has to be before the next forward signal
5156 /*
5157  if((Track->TrackElementAt(411, CurrentTrackVectorPosition).ActiveTrackElementName == "") && (PlatformFoundFlag))
5158  {
5159  Utilities->CallLogPop(719);
5160  return false;
5161  }
5162 */
5163  // make sure points are followed correctly (d) & set ExitPos
5164  if(CurrentTrackElement.TrackType == Points)
5165  {
5166  if((EntryPos == 0) || (EntryPos == 2))
5167  {
5168  if(CurrentTrackElement.Attribute == 0)
5169  {
5170  ExitPos = 1;
5171  }
5172  else
5173  {
5174  ExitPos = 3;
5175  }
5176  }
5177  if(EntryPos == 1)
5178  {
5179  if(CurrentTrackElement.Attribute == 0)
5180  {
5181  ExitPos = 0;
5182  }
5183  else
5184  {
5185  Utilities->CallLogPop(720);
5186  return(false);
5187  }
5188  }
5189  if(EntryPos == 3)
5190  {
5191  if(CurrentTrackElement.Attribute == 1)
5192  {
5193  ExitPos = 0;
5194  }
5195  else
5196  {
5197  Utilities->CallLogPop(721);
5198  return(false);
5199  }
5200  }
5201  }
5202  else
5203  {
5204  ExitPos = Track->GetNonPointsOppositeLinkPos(EntryPos);
5205  }
5206  // check existing routes - if element forward of the signal (ElementNumber == 2) is AutoSignals then OK without further checks as this route must extend to
5207  // the next signal so must at least reach the station, also if have another route set (must be unrestricted) from either the stop signal or the element after it
5208  // to or towards the platform (& all points set correctly) then OK, otherwise reject if (1) there are any route elements already set from element
5209  // forward of element after the signal to & including the first platform element (covers crossover with other route set) or (2) a fouled diagonal (k)
5210  if(ElementNumber < 2)
5211  {
5212  SkipRouteCheck = true;
5213  }
5214  else
5215  {
5216  SkipRouteCheck = false;
5217  }
5218  if(ElementNumber == 1) // the stop signal
5219  {
5220  RouteStartPosition = CurrentTrackVectorPosition;
5221  }
5222 /*
5223  if(ElementNumber == 2)
5224  {
5225  if(AllRoutes->GetRouteTypeAndNumber(18, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->AutoSigsRoute) AutoSigs = true;
5226  else AutoSigs = false;
5227  if(AllRoutes->GetRouteTypeAndNumber(25, CurrentTrackVectorPosition, EntryPos, RouteID) == AllRoutes->NotAutoSigsRoute) OtherFullRouteSet = true;
5228  }
5229 */
5230  if(ElementNumber > 1)
5231  {
5232  if(AllRoutes->GetRouteTypeAndNumber(26, CurrentTrackVectorPosition, EntryPos, RouteID) != AllRoutes->NoRoute)
5233  {
5234  RouteOrPartRouteSet = true;
5235  }
5236  else
5237  {
5238  RouteOrPartRouteSet = false;
5239  }
5240  }
5241  if(!SkipRouteCheck && !RouteOrPartRouteSet)
5242  {
5243  if(AllRoutes->TrackIsInARoute(16, CurrentTrackVectorPosition, EntryPos)) // must be a conflicting route
5244  {
5245  Utilities->CallLogPop(1859);
5246  return(false);
5247  }
5248  int ExitLink = CurrentTrackElement.Link[ExitPos];
5249  if((ExitLink == 1) || (ExitLink == 3) || (ExitLink == 7) || (ExitLink == 9))
5250  {
5251  if(AllRoutes->DiagonalFouledByRouteOrTrain(6, CurrentTrackElement.HLoc, CurrentTrackElement.VLoc, ExitLink))
5252  {
5253  Utilities->CallLogPop(1850);
5254  return(false);
5255  }
5256  }
5257  }
5258  // finished all checks, now update CurrentTrackVectorPosition & EntryPos for the next iteration
5259  if(EntryPos < 2)
5260  {
5261  Distance += CurrentTrackElement.Length01;
5262  }
5263  else
5264  {
5265  Distance += CurrentTrackElement.Length23;
5266  }
5267  NextTrackVectorPosition = CurrentTrackElement.Conn[ExitPos];
5268  NextEntryPos = CurrentTrackElement.ConnLinkPos[ExitPos];
5269  CurrentTrackVectorPosition = NextTrackVectorPosition;
5270  EntryPos = NextEntryPos;
5271  ElementNumber++;
5272  } // while(true)
5273 
5274  // if all OK & autosigs route not already set then set an unrestricted route into the station (just to the first platform)
5275  // from the stop signal (note that it may be last in an autosigs route)
5276  // a single element route at the stop signal should have been removed prior to this function being called (that called before
5277  // this in ClockTimer2)
5278 
5279  // now add elements to the CallonVector
5280  TAllRoutes::TCallonEntry CallonEntry(RouteOrPartRouteSet, RouteStartPosition, PlatformPosition);
5281 
5282  AllRoutes->CallonVector.push_back(CallonEntry);
5283  Utilities->CallLogPop(1860);
5284  return(true); // return false if fail to set route for any reason
5285 }
5286 
5287 // ---------------------------------------------------------------------------
5288 /*
5289  bool TTrain::TimetableFinished()
5290  {
5291  if((ActionVectorEntryPtr == TrainDataEntryPtr->ActionVector.end()) || (ActionVectorEntryPtr->FormatType == Repeat))//past all actions
5292  {
5293  return true;
5294  }
5295  return false;
5296  }
5297 */
5298 // ---------------------------------------------------------------------------
5299 
5300 AnsiString TTrain::GetTrainHeadCode(int Caller)
5301 
5302 {
5303  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainHeadCode" + "," + HeadCode);
5304  AnsiString RepeatHeadCode = TrainController->GetRepeatHeadCode(0, HeadCode, RepeatNumber, IncrementalDigits);
5305 
5306  Utilities->CallLogPop(1452);
5307  return(RepeatHeadCode);
5308 }
5309 
5310 // ---------------------------------------------------------------------------
5311 
5312 TDateTime TTrain::GetTrainTime(int Caller, TDateTime Time)
5313 {
5314  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetTrainTime," + Utilities->Format96HHMMSS(Time));
5315  TDateTime RepeatTime = TrainController->GetRepeatTime(1, Time, RepeatNumber, IncrementalMinutes);
5316 
5317  Utilities->CallLogPop(1453);
5318  return(RepeatTime);
5319 }
5320 
5321 // ---------------------------------------------------------------------------
5322 
5323 bool TTrain::IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
5324 {
5325  // Used to check for a stopped adjacent train for use in PopUp menu //new at v2.4.0
5326  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsThereAnAdjacentTrain" + "," + HeadCode);
5327  // check if there's a stopped adjacent train, if there is but not under sig control give a message in calling function
5328  // first check that train is fully on the railway
5329  bool FrontValid = false, RearValid = false;
5330  TTrackElement FrontAdjacentTrackElement, RearAdjacentTrackElement;
5331 
5332  if((LeadElement == -1) || (MidElement == -1))
5333  {
5334  TrainToBeJoinedBy = NULL;
5335  Utilities->CallLogPop(2131);
5336  return(false);
5337  }
5339  {
5340  FrontAdjacentTrackElement = Track->TrackElementAt(965, (Track->TrackElementAt(966, LeadElement).Conn[LeadExitPos]));
5341  FrontValid = true;
5342  }
5344  {
5345  RearAdjacentTrackElement = Track->TrackElementAt(968, (Track->TrackElementAt(969, MidElement).Conn[MidEntryPos]));
5346  RearValid = true;
5347  }
5348  int TrainToBeJoinedByID = -1;
5349 
5350  // first check if on a 2-track element & select correct ID number if so
5351  if(FrontValid)
5352  {
5353  if(FrontAdjacentTrackElement.TrackType == Bridge)
5354  {
5356  {
5357  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5358  }
5359  else
5360  {
5361  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5362  }
5363  }
5364  else
5365  {
5366  TrainToBeJoinedByID = FrontAdjacentTrackElement.TrainIDOnElement;
5367  }
5368  }
5369  if((TrainToBeJoinedByID < 0) && RearValid)
5370  {
5371  // first check if on a 2-track element & select correct ID number if so
5372  if(RearAdjacentTrackElement.TrackType == Bridge)
5373  {
5375  {
5376  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23;
5377  }
5378  else
5379  {
5380  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01;
5381  }
5382  }
5383  else
5384  {
5385  TrainToBeJoinedByID = RearAdjacentTrackElement.TrainIDOnElement;
5386  }
5387  }
5388  if(TrainToBeJoinedByID < 0) // no adjacent train
5389  {
5390  TrainToBeJoinedBy = NULL;
5391  Utilities->CallLogPop(2132);
5392  return(false);
5393  }
5394  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(44, TrainToBeJoinedByID));
5395  if(!TrainToBeJoinedBy->Stopped())
5396  {
5397  TrainToBeJoinedBy = NULL;
5398  Utilities->CallLogPop(2133);
5399  return(false);
5400  }
5401  Utilities->CallLogPop(2134);
5402  return(true);
5403 }
5404 
5405 // ---------------------------------------------------------------------------
5406 
5407 void TTrain::LogAction(int Caller, AnsiString OwnHeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution,
5408  TDateTime TimetableNonRepeatTime, bool Warning)
5409 /*
5410  Time = timetable time, the time adjustments for repeat trains is carried out internally
5411  Not all messages need this, if not needed a dummy value is required but not used
5412 
5413  Arrive: 06:05:40: 2F46 arrived at Old Street 1 minute late
5414  Pass: 06:05:40: 2F46 passed Old Street 1 minute late
5415  Terminate: 06:05:40: 2F46 terminated at Old Street 1 minute late <-- sent from RemainHere as LogAction not called for terminate
5416  //NB for Frh just give terminated message but without event time - don't use this function
5417  Depart: 06:05:15: 3F43 departed from Essex Road 2 minutes late
5418  Create: 06:05:40: 2F46 created at Old Street 1 minute late
5419  Enter: 06:05:40: 2F46 entered railway at Old Street 1 minute late
5420  Leave: 06:05:40: 2F46 left railway at 57-N4 1 minute late
5421  FrontSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from front to 3D54 at Old Street 1 minute late
5422  RearSplit: 06:05:40: 2F46 split mass%-Power% = 10-50 from rear to 3D54 at Old Street 1 minute late
5423  JoinedByOther: 06:05:40: 2F46 joined by 3D54 at Old Street 1 minute late
5424  ChangeDirection: 06:05:40: 2F46 changed direction at Old Street 1 minute late
5425  ChangeDescription: 06:05:40: 2F46 changed its description to 'NewDescription' at Old Street 1 minute late
5426  NewService: 06:05:40: 2F46 became new service 3D54 at Old Street 1 minute late
5427  TakeManualControl: 06:05:40: 2F46 taken under signaller control at Old Street
5428  RestoreTimetableControl: 06:05:40: 2F46 restored to timetable control at Old Street
5429  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO CRASH at Old Street
5430  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY DUE TO DERAILMENT at Old Street
5431  RemoveTrain: 06:05:40: 2F46 REMOVED FROM RAILWAY at Old Street
5432  SignallerMoveForwards 06:05:40: 2F46 received signaller authority to proceed
5433  SignallerChangeDirection 06:05:40: 2F46 changed direction under signaller control at Old Street
5434  SignallerPassRedSignal 06:05:40: 2F46 received signaller authority to pass stop signal
5435  SignallerJoin 06:05:40: 2F46 joined under signaller control by 3D54 at Old Street //new at v2.4.0
5436  TrainFailure 06:05:40: 2F46 suffered an onboard power failure at Old Street //new at v2.4.0
5437  RepairFailedTrain 06:05:40: 2F46 failure repaired at Old Street //new at v2.4.0
5438  SignallerControlStop 06:05:40: 2F46 received signaller instruction to stop
5439  SignallerStop 06:05:40: 2F46 stopped on signaller command
5440  SignallerLeave: 06:05:40: 2F46 left railway under signaller control at 57-N4
5441  SignallerStepForward: 06:05:40: 2F46 received signaller authority to step forward
5442 */{
5443  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogAction," + OwnHeadCode + "," + OtherHeadCode + "," +
5444  AnsiString(ActionType) + "," + LocationName + "," + HeadCode);
5445  AnsiString BaseLog = "", WarningBaseLog = "", ReminderBaseLog = "", PerfLog = "", ActionLog = "";
5446  int IntMinsLate = 0;
5447  bool TTEvent = false; //indicates a timetabled event, prevents reminders for non-tt events where these set for next tt event
5448  //don't need it for warnings as these passed in from appropriate calling functions
5449  // need to set it in case MinsLate == 0, since it isn't tested for that
5450  if(ActionType == Arrive)
5451  {
5452  ActionLog = " arrived at ";
5453  TTEvent = true;
5454  }
5455  if(ActionType == Terminate) //redundant as Logaction not called for terminate - RemainHere deals with logging for terminate
5456  {
5457  if(TerminatedMessageSent) // to avoid it being sent twice
5458  {
5459  Utilities->CallLogPop(1104);
5460  return;
5461  }
5462  ActionLog = " terminated at ";
5463  TTEvent = true;
5464  TerminatedMessageSent = true;
5465  }
5466  if(ActionType == Depart)
5467  {
5468  ActionLog = " departed from ";
5469  TTEvent = true;
5470  }
5471  if(ActionType == Pass)
5472  {
5473  ActionLog = " passed ";
5474  TTEvent = true;
5475  }
5476  if(ActionType == Create)
5477  {
5478  ActionLog = " created at ";
5479  }
5480  if(ActionType == Enter)
5481  {
5482  ActionLog = " entered railway at ";
5483  }
5484  if(ActionType == ChangeDescription)
5485  {
5486  ActionLog = " changed its description to '" + Description + "' at "; //changed to train description at v2.16.1
5487  TTEvent = true;
5488  }
5489  if(ActionType == Leave)
5490  {
5491  ActionLog = " left railway at ";
5492  TTEvent = true;
5493  }
5494  if(ActionType == FrontSplit)
5495  {
5496  ActionLog = " split mass%-Power% = " + SplitDistribution + " from front to ";
5497  TTEvent = true;
5498  }
5499  if(ActionType == RearSplit)
5500  {
5501  ActionLog = " split mass%-Power% = " + SplitDistribution + " from rear to ";
5502  TTEvent = true;
5503  }
5504  if(ActionType == JoinedByOther)
5505  {
5506  ActionLog = " joined by ";
5507  TTEvent = true;
5508  }
5509  if(ActionType == ChangeDirection)
5510  {
5511  ActionLog = " changed direction at ";
5512  TTEvent = true;
5513  }
5514  if(ActionType == NewService)
5515  {
5516  ActionLog = " became new service ";
5517  TTEvent = true;
5518  }
5519  if(ActionType == TakeSignallerControl)
5520  {
5521  ActionLog = " taken under signaller control at ";
5522  }
5523  if(ActionType == RestoreTimetableControl)
5524  {
5525  ActionLog = " restored to timetable control at ";
5526  }
5527  if(ActionType == RemoveTrain)
5528  {
5529  if(Crashed)
5530  {
5531  ActionLog = " REMOVED FROM RAILWAY DUE TO CRASH at ";
5532  }
5533  else if(Derailed)
5534  {
5535  ActionLog = " REMOVED FROM RAILWAY DUE TO DERAILMENT at ";
5536  }
5537  else
5538  {
5539  ActionLog = " REMOVED FROM RAILWAY at ";
5540  }
5541  }
5542  if(ActionType == SignallerMoveForwards)
5543  {
5544  ActionLog = " received signaller authority to proceed";
5545  }
5546  if(ActionType == SignallerStepForward)
5547  {
5548  ActionLog = " received signaller authority to step forward";
5549  }
5550  if(ActionType == SignallerChangeDirection)
5551  {
5552  ActionLog = " changed direction under signaller control at ";
5553  }
5554  if(ActionType == SignallerPassRedSignal)
5555  {
5556  ActionLog = " received signaller authority to pass stop signal";
5557  }
5558  if(ActionType == SignallerControlStop)
5559  {
5560  ActionLog = " received signaller instruction to stop";
5561  }
5562  if(ActionType == SignallerStop)
5563  {
5564  ActionLog = " stopped on signaller instruction ";
5565  }
5566  if(ActionType == SignallerJoin)
5567  {
5568  ActionLog = " joined under signaller control by ";
5569  }
5570  if(ActionType == TrainFailure)
5571  {
5572  ActionLog = " suffered an onboard power failure at ";
5573  }
5574  if(ActionType == RepairFailedTrain)
5575  {
5576  ActionLog = " failure repaired at ";
5577  }
5578  if(ActionType == SignallerLeave)
5579  {
5580  ActionLog = " left railway under signaller control at ";
5581  }
5582  if(OtherHeadCode != "")
5583  {
5584  OtherHeadCode += " at ";
5585  }
5586  TDateTime ActualTime = TrainController->TTClockTime;
5587 
5588  if(Warning)
5589  {
5590  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5591  WarningBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5592  }
5593  else //added at v2.19.0
5594  {
5595  if(TTEvent && ((ActionVectorEntryPtr->Reminder == 1) || (ActionVectorEntryPtr->Reminder == 4)))
5596  {
5597  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5598  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5599  }
5600  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 2) && (ActionLog == " departed from ")) //depart only
5601  {
5602  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5603  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5604  }
5605  else if(TTEvent && (ActionVectorEntryPtr->Reminder == 3) && (ActionLog == " arrived at ")) //arrive only
5606  {
5607  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5608  ReminderBaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName; //added time at v2.13.0
5609  }
5610  else
5611  {
5612  BaseLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + ActionLog + OtherHeadCode + LocationName;
5613  }
5614  }
5615 
5616  bool TimePerformance = true;
5617 
5618  if((ActionType == TakeSignallerControl) || (ActionType == RestoreTimetableControl) || (ActionType == RemoveTrain) || (ActionType == SignallerMoveForwards)
5619  || (ActionType == SignallerChangeDirection) || (ActionType == SignallerPassRedSignal) || (ActionType == SignallerControlStop) ||
5620  (ActionType == SignallerStop) || (ActionType == SignallerLeave) || (ActionType == SignallerStepForward) || (ActionType == SignallerJoin) ||
5621  (ActionType == TrainFailure) || (ActionType == RepairFailedTrain))
5622  // SignallerJoin & RepairFailedTrain new at v2.4.0
5623  {
5624  TimePerformance = false;
5625  }
5626  if(TimePerformance)
5627  {
5628  double MinsLate = ((double)(ActualTime - GetTrainTime(1, TimetableNonRepeatTime))) * 1440;
5629  MinsDelayed = float(MinsLate);
5630  if(ActionType == Pass) //added at v2.9.2 to prevent time to act increasing suddenly for early pass times then becoming 'NOW' when stops at signal
5631  {
5632  MinsDelayed = 0;
5633  }
5634  // new v2.2.0 for OpActionPanel, can be positive or negative
5635  if(ActionType == Arrive)
5636  {
5638  }
5639  // since train has just arrived this value is the
5640  // recoverable time available at this stop, so reduce MinsDelayed by this amount to prevent it being
5641  // subtracted from later stop recoverable times.
5642  if(MinsLate < 0)
5643  {
5644  IntMinsLate = int(ceil(MinsLate));
5645  }
5646  if(MinsLate > 0)
5647  {
5648  IntMinsLate = int(floor(MinsLate));
5649  }
5650  if(IntMinsLate == 0)
5651  {
5652  PerfLog = " on time";
5653  }
5654  else if(IntMinsLate == 1)
5655  {
5656  PerfLog = " 1 minute late";
5657  }
5658  else if(IntMinsLate == -1)
5659  {
5660  PerfLog = " 1 minute early";
5661  }
5662  else if(IntMinsLate > 1)
5663  {
5664  PerfLog = " " + AnsiString(IntMinsLate) + " minutes late";
5665  }
5666  else if(IntMinsLate < -1)
5667  {
5668  int PosIntMinsLate = -IntMinsLate;
5669  PerfLog = " " + AnsiString(PosIntMinsLate) + " minutes early";
5670  }
5671  if(LocationName.Pos('-') > 0)
5672  {
5673  PerfLog = "," + PerfLog;
5674  // if a position add a comma to separate vertical position number from number of minutes (better appearance)
5675  }
5676  PerfLogForm->PerformanceLog(0, BaseLog + PerfLog);
5677  }
5678  else
5679  {
5680  PerfLogForm->PerformanceLog(1, BaseLog);
5681  }
5682  if(Warning)
5683  {
5684  Display->WarningLog(0, WarningBaseLog);
5685  }
5686  if(ReminderBaseLog != "") //added at v2.19.0
5687  {
5688  Display->WarningLog(24, ReminderBaseLog);
5689  ReminderBaseLog = ""; //reset to null
5690  ActionVectorEntryPtr->Reminder = 0; //to prevent reminders for repeats
5691  }
5692  // update statistics
5693  if((ActionType == Arrive) && (IntMinsLate == 0))
5694  {
5696  }
5697  else if((ActionType == Arrive) && (IntMinsLate > 0))
5698  {
5700  TrainController->TotLateArrMins += IntMinsLate;
5701  }
5702  else if((ActionType == Arrive) && (IntMinsLate < 0))
5703  {
5705  TrainController->TotEarlyArrMins += abs(IntMinsLate);
5706  }
5707 
5708  else if((ActionType == Pass) && (IntMinsLate == 0))
5709  {
5711  }
5712  else if((ActionType == Pass) && (IntMinsLate > 0))
5713  {
5715  TrainController->TotLatePassMins += IntMinsLate;
5716  }
5717  else if((ActionType == Pass) && (IntMinsLate < 0))
5718  {
5720  TrainController->TotEarlyPassMins += abs(IntMinsLate);
5721  }
5722 
5723  else if((ActionType == Leave) && (IntMinsLate == 0)) //new at v2.9.1 as had been omitted in error earlier
5724  {
5726  }
5727  else if((ActionType == Leave) && (IntMinsLate > 0))
5728  {
5730  TrainController->TotLateExitMins += IntMinsLate;
5731  }
5732  else if((ActionType == Leave) && (IntMinsLate < 0))
5733  {
5735  TrainController->TotEarlyExitMins += abs(IntMinsLate);
5736  }
5737 
5738  else if((ActionType == Depart) && (IntMinsLate == 0)) //can't depart early
5739  {
5741  }
5742  else if((ActionType == Depart) && (IntMinsLate > 0))
5743  {
5745  TrainController->TotLateDepMins += IntMinsLate;
5746  }
5747  Utilities->CallLogPop(968);
5748 }
5749 
5750 // ---------------------------------------------------------------------------
5751 
5752 void TTrain::TrainHasFailed(int Caller)
5753 {
5754  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainHasFailed," + HeadCode);
5755  if(Crashed || Derailed || DerailPending)
5756  {
5757  TrainFailurePending = false;
5758  Utilities->CallLogPop(2135);
5759  return;
5760  }
5761  AnsiString LocName = "";
5762 
5763  if(LeadElement > -1)
5764  {
5766  }
5767  if((LocName == "") && (MidElement > -1))
5768  {
5770  }
5771  if((LocName == "") && LeadElement > -1)
5772  {
5773  LocName = Track->TrackElementAt(974, LeadElement).ElementID;
5774  }
5775  if((LocName == "") && (MidElement > -1))
5776  {
5777  LocName = Track->TrackElementAt(975, MidElement).ElementID;
5778  }
5779  TrainController->StopTTClockMessage(81, HeadCode + " has suffered an onboard power failure at " + LocName);
5780  TrainFailed = true;
5781  TrainFailurePending = false;
5782  CallingOnFlag = false; //added at v2.10.0
5784  PowerAtRail = 0.08;
5785  AValue = sqrt(2 * PowerAtRail / Mass);
5787  // TrainFailed only called when PlotElements properly set to Lead, Mid & Lag elements
5788  if(Stopped())
5789  {
5790  EntrySpeed = 0;
5791  ExitSpeedHalf = 0;
5792  ExitSpeedFull = 0;
5793  MaxExitSpeed = 0;
5794  BrakeRate = 0;
5795  StoppedWithoutPower = true;
5796  }
5798  LogAction(33, HeadCode, "", TrainFailure, LocName, "", TDateTime(0), true);
5799  // true for warning, TDateTime not used
5800  Utilities->CallLogPop(2136);
5801 }
5802 
5803 // ---------------------------------------------------------------------------
5804 
5805 void TTrain::FrontTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
5806 {
5807 /*
5808  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
5809  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
5810  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
5811  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
5812 */
5813  TrainController->LogEvent("" + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
5814  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FrontTrainSplit" + "," + HeadCode);
5815 
5816 /* restriction removed at v2.19.0
5817  if(PowerAtRail < 1)
5818  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
5819  {
5820  if(!ZeroPowerNoFrontSplitMessage)
5821  {
5822  TrainController->StopTTClockMessage(82, HeadCode + ": A train without power can't split");
5823  }
5824  ZeroPowerNoFrontSplitMessage = true;
5825  Utilities->CallLogPop(2137);
5826  return;
5827  }
5828 */
5829 
5830  AnsiString LocationName = Track->TrackElementAt(555, LeadElement).ActiveTrackElementName;
5831 
5832  if(LocationName == "")
5833  {
5834  LocationName = Track->TrackElementAt(837, MidElement).ActiveTrackElementName;
5835  }
5836  int RearTrainRearPos, RearTrainFrontPos, RearTrainExitPos;
5837  int FrontTrainRearPos, FrontTrainFrontPos;
5839 
5840  if(LocationName == "")
5841  {
5842  throw Exception("Error - LocationName not set in FrontTrainSplit");
5843  }
5844  // if message given call at ~5 sec intervals in case train repositioned
5845 
5846  bool TemporaryDelay = false;
5848  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
5849  {
5851  {
5853  {
5854  TrainController->StopTTClockMessage(151, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
5855  TrainController->LogActionError(6, HeadCode, "", FailLocTooShort, LocationName);
5857  }
5858  }
5859  Utilities->CallLogPop(1009); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
5860  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
5861  } //found & not reported. Same for RearTrainSplit
5862 
5863  if(TemporaryDelay)
5864  {
5866  Utilities->CallLogPop(2683);
5867  return;
5868  }
5869 
5870 //it is long enough for split
5871  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
5872  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
5873  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
5875 
5876  UnplotTrain(0);
5877  StartSpeed = 0;
5878  RearStartElement = RearTrainRearPos; //this is for the current train, not the new train which will attach to the front of this train
5879  for(int x = 0; x < 4; x++)
5880  {
5881  if(Track->TrackElementAt(1664, RearStartElement).Conn[x] == RearTrainFrontPos)
5882  {
5883  RearStartExitPos = x;
5884  }
5885  }
5886  StoppedAtLocation = true;
5887  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
5888  {
5889  StoppedWithoutPower = true;
5890  }
5891  PlotStartPosition(3);
5894 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
5896 
5897  //new at v2.15.0 for unequal split in mass & power
5898  int NewTrainMass;
5899  double NewTrainPowerAtRail;
5901  {
5902  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
5903  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
5904  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
5905  NewTrainMass = Mass * double(MassPercent)/100.0;
5906  Mass = Mass - NewTrainMass;
5907  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
5908  if(NewTrainPowerAtRail == 0)
5909  {
5910  NewTrainPowerAtRail = 0.08; //min value represents 0
5911  }
5912  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
5913  AValue = sqrt(2 * PowerAtRail / Mass);
5914  }
5915  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
5916  {
5917  Mass = Mass / 2;
5918  NewTrainMass = Mass;
5919  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
5920  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
5921  // and when needed it's calculated from rate & mass - changed at v2.15.0
5922  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
5923  PowerAtRail = PowerAtRail / 2;
5924  NewTrainPowerAtRail = PowerAtRail;
5925  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
5926  AValue = sqrt(2 * PowerAtRail / Mass);
5927  // shouldn't change but include in case not set earlier
5928  }
5929 
5930  TActionEventType EventType = NoEvent;
5931  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
5932  //may be repositioned so all references to this train may be invalid
5933 
5934  if(!TrainController->AddTrain(0, FrontTrainRearPos, FrontTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
5935  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
5936  // false for SignallerControl
5937  {
5938  Utilities->CallLogPop(1721); // EventType not used here
5939  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
5940  // another train, in which case a message will have been sent to the perf log, also might well clear later
5941  // when other train moves away
5942  return;
5943  }
5944 
5945  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
5946  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
5947  {
5948 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
5949  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
5950  }
5951 
5952  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
5953  // see mods in UpdateTrain for v1.3.2
5954  TrainController->TrainAdded = true;
5955 
5956  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
5957 
5958  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
5959  TTOD.RunningEntry = Running;
5960  Utilities->CallLogPop(998);
5961 }
5962 
5963 // ---------------------------------------------------------------------------
5964 
5965 void TTrain::RearTrainSplit(int Caller) //Major rewrite at v2.18.0 using new ThisLocationLongEnoughForSplit
5966 {
5967 /*
5968  Split logic for is:- the final 4 train positions must overlap with the original train, & final 4 positions
5969  will maximise the number at the location. Note that this function isn't sophisticated enough to account for trains already at the
5970  location in determining the 4 positions, and will give a failure message if a train obstructs any of the 4 positions. In these
5971  circumstances the other train will need to be moved sufficiently away to release all 4 positions, then the train will split.
5972 */
5973  TrainController->LogEvent("" + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
5974  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RearTrainSplit" + "," + HeadCode);
5975 
5976 /* restriction removed at v2.19.0
5977  if(PowerAtRail < 1)
5978  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can split when power restored
5979  {
5980  if(!ZeroPowerNoRearSplitMessage)
5981  {
5982  TrainController->StopTTClockMessage(176, HeadCode + ": A train without power can't split");
5983  }
5984  ZeroPowerNoRearSplitMessage = true;
5985  Utilities->CallLogPop(2685);
5986  return;
5987  }
5988 */
5989  AnsiString LocationName = Track->TrackElementAt(1676, LeadElement).ActiveTrackElementName;
5990 
5991  if(LocationName == "")
5992  {
5993  LocationName = Track->TrackElementAt(1677, MidElement).ActiveTrackElementName;
5994  }
5995  int RearTrainRearPos, RearTrainFrontPos;
5996  int FrontTrainRearPos, FrontTrainFrontPos, FrontTrainExitPos;
5998 
5999  if(LocationName == "")
6000  {
6001  throw Exception("Error - LocationName not set in RearTrainSplit");
6002  }
6003  // if message given call at ~5 sec intervals in case train repositioned
6004 
6005  bool TemporaryDelay = false;
6007  MidEntryPos, FrontTrainFrontPos, FrontTrainRearPos, RearTrainFrontPos, RearTrainRearPos, TemporaryDelay))
6008  {
6010  {
6012  {
6013  TrainController->StopTTClockMessage(177, HeadCode + " failed to split - location too short at " + LocationName + ", reposition train if possible.");
6014  TrainController->LogActionError(66, HeadCode, "", FailLocTooShort, LocationName);
6016  }
6017  }
6018  Utilities->CallLogPop(2686); //these were inside above bracket & caused own detected fault on second access as didn't return, moved here at v2.19.0
6019  return; //this has been here for a very long time (at least from 2.0.0) but never reported! Either never reached this point or
6020  } //found & not reported. Same for FrontTrainSplit
6021 
6022  if(TemporaryDelay)
6023  {
6025  Utilities->CallLogPop(2684);
6026  return;
6027  }
6028 
6029 //it is long enough for split
6030  AnsiString SplitTrainFixedDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->FixedDescription; //save these for new train before ActionVectorEntryPtr incremented
6031  AnsiString SplittingTrainDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6032  bool SplitTrainExplicitDescription = ActionVectorEntryPtr->LinkedTrainEntryPtr->ExplicitDescription;
6034 
6035  UnplotTrain(11);
6036  StartSpeed = 0;
6037  RearStartElement = FrontTrainRearPos; //this is for the current train, not the new train which will attach to the rear of this train
6038  for(int x = 0; x < 4; x++)
6039  {
6040  if(Track->TrackElementAt(1665, RearStartElement).Conn[x] == FrontTrainFrontPos)
6041  {
6042  RearStartExitPos = x;
6043  }
6044  }
6045  StoppedAtLocation = true;
6046  if((PowerAtRail < 1) && EntrySpeed < 1) // added at v2.4.0
6047  {
6048  StoppedWithoutPower = true;
6049  }
6050  PlotStartPosition(12);
6053 // ActionVectorEntryPtr++; moved lower down at v2.15.0 because of new section below
6055 
6056  //new at v2.15.0 for unequal split in mass & power
6057  int NewTrainMass;
6058  double NewTrainPowerAtRail;
6060  {
6061  int pos = ActionVectorEntryPtr->SplitDistribution.Pos('-');
6062  int MassPercent = ActionVectorEntryPtr->SplitDistribution.SubString(1, pos - 1).ToInt(); //validity checked during validation
6063  int PowerPercent = ActionVectorEntryPtr->SplitDistribution.SubString(pos + 1, ActionVectorEntryPtr->SplitDistribution.Length() - pos).ToInt();
6064  NewTrainMass = Mass * double(MassPercent)/100.0;
6065  Mass = Mass - NewTrainMass;
6066  NewTrainPowerAtRail = PowerAtRail * double(PowerPercent)/100.0;
6067  if(NewTrainPowerAtRail == 0)
6068  {
6069  NewTrainPowerAtRail = 0.08; //min value represents 0
6070  }
6071  PowerAtRail = PowerAtRail - NewTrainPowerAtRail;
6072  AValue = sqrt(2 * PowerAtRail / Mass);
6073  }
6074  else // same Mass, MaxBrakeRate & PowerAtRail as this train's halved values, and same MaxRunningSpeed as this train
6075  {
6076  Mass = Mass / 2;
6077  NewTrainMass = Mass;
6078  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).Mass = Mass;
6079  // MaxBrakeRate = MaxBrakeRate / 2; this was wrong - want brake rate to stay the same, brake force is halved but that not a train parameter
6080  // and when needed it's calculated from rate & mass - changed at v2.15.0
6081  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).MaxBrakeRate = MaxBrakeRate;
6082  PowerAtRail = PowerAtRail / 2;
6083  NewTrainPowerAtRail = PowerAtRail;
6084  // TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).PowerAtRail = PowerAtRail;
6085  AValue = sqrt(2 * PowerAtRail / Mass);
6086  // shouldn't change but include in case not set earlier
6087  }
6088 
6089  TActionEventType EventType = NoEvent;
6090  ActionVectorEntryPtr++; //moved here at v2.18.0 to give more chances to split in case points set wrongly initially, also when AddTrain TrainVector
6091  //may be repositioned so all references to this train may be invalid
6092 
6093  if(!TrainController->AddTrain(4, RearTrainRearPos, RearTrainFrontPos, OtherHeadCode, 0, NewTrainMass, MaxRunningSpeed, MaxBrakeRate, NewTrainPowerAtRail,
6094  "Timetable", LinkedTrainEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerMaxSpeed, false, EventType))
6095  // false for SignallerControl
6096  {
6097  Utilities->CallLogPop(2687); // EventType not used here
6098  // if fails either a throw will have been sent in AddTrain or start position failed prob because of
6099  // another train, in which case a message will have been sent to the perf log, also might well clear later
6100  // when other train moves away
6101  return;
6102  }
6103 
6104  TrainController->TrainVector.back().Description = SplitTrainFixedDescription; //added at v2.16.1, new train takes description from its TrainDataEntry
6105  if(!SplitTrainExplicitDescription) //new at v2.15.0 see above
6106  {
6107 // OldActionVectorEntryPtr->LinkedTrainEntryPtr->Description = OriginalDescription; dropped at v2.16.1
6108  TrainController->TrainVector.back().Description = SplittingTrainDescription; //else takes it from this train's description
6109  }
6110 
6111  // Note data in 'this' now probably invalid as there has been a new addition to the TrainVector, so the train is likely to have a new address, hence make no more changes for the current train
6112  // see mods in UpdateTrain for v1.3.2
6113  TrainController->TrainAdded = true;
6114 
6115  TTrainOperatingData &TTOD = LinkedTrainEntryPtr->TrainOperatingDataVector.at(RepeatNumber); // this is for the newly created train
6116 
6117  TTOD.TrainID = TrainController->TrainVector.back().TrainID;
6118  TTOD.RunningEntry = Running;
6119  Utilities->CallLogPop(2688);
6120 }
6121 
6122 // ---------------------------------------------------------------------------
6123 
6124 void TTrain::FinishJoin(int Caller)
6125 {
6126  if(FinishJoinLogSent == false)
6127  {
6128  TrainController->LogEvent("" + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6129  FinishJoinLogSent = true; // so don't keep logging this event
6130  // don't need to reset it to false after the event as the train is deleted
6131  }
6132  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishJoin" + "," + HeadCode);
6133  if(TrainFailed)
6134  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when repaired) Can FinishJoin if zero power & not failed, as for empty stock
6135  {
6137  {
6138  TrainController->StopTTClockMessage(84, HeadCode + ": A failed train can't join another under timetable control");
6139  }
6141  Utilities->CallLogPop(2139);
6142  return;
6143  }
6144  if(TrainGone)
6145  // this means that the train has already joined the other & is awaiting deletion by TrainController
6146  // without this the 'waiting' message can be given since the other train's ActionVectorEntryPtr has moved
6147  // on from jbo & TrainToJoinIsAdjacent returns false
6148  {
6149  Utilities->CallLogPop(1035);
6150  return;
6151  }
6152  TTrain *TrainToJoin;
6154 
6155  if(!TrainToJoinIsAdjacent(0, TrainToJoin))
6156  {
6158  {
6159  // PerfLogForm->PerformanceLog(2, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to join " + JBOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6162  }
6163  Utilities->CallLogPop(1030);
6164  return; // keep this here in case need to add code before final return
6165  }
6166  // no need to clear error report flag here, cleared in jbo function
6167  // No need to set TimetableFinished, done in jbo function
6168  Utilities->CallLogPop(1031);
6169 }
6170 
6171 // ---------------------------------------------------------------------------
6172 
6173 void TTrain::JoinedBy(int Caller)
6174 {
6175  if(TrainController->OpTimeToActUpdateCounter == 0) //added at v2.13.2. Use OpTimeToActUpdateCounter for convenience so only issue the event log
6176  //once every second rather than many times. Can't use an event logged flag because there may
6177  //be several trains that are to be joined by others
6178  {
6179  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Waiting to be joined");
6180  }
6181  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",JoinedBy" + "," + HeadCode);
6182 
6183 /* restriction removed at v2.19.0
6184  if(PowerAtRail < 1)
6185  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can join when power restored)
6186  {
6187  if(!ZeroPowerNoJoinedByMessage)
6188  {
6189  TrainController->StopTTClockMessage(85, HeadCode + ": A train without power can't be joined by another under timetable control");
6190  }
6191  ZeroPowerNoJoinedByMessage = true;
6192  Utilities->CallLogPop(2140);
6193  return;
6194  }
6195 */
6196  TTrain *TrainToBeJoinedBy;
6198 
6199  if(!TrainToBeJoinedByIsAdjacent(0, TrainToBeJoinedBy))
6200  {
6202  {
6203  // PerfLogForm->PerformanceLog(3, TrainController->TTClockTime.FormatString("hh:nn:ss") + ": " + HeadCode + " waiting to be joined by " + FJOHeadCode + " at " + ActionVectorEntryPtr->LocationName);
6206  }
6207  LastActionDelayFlag = true;
6208  // need to update LastActionTime if this train first to arrive as need 30s after
6209  // both adjacent before the join
6210  Utilities->CallLogPop(1032);
6211  return;
6212  }
6213  // here when other train is adjacent
6215  {
6217  // need to update this as need 30s after both adjacent before the join
6218  LastActionDelayFlag = false;
6219  Utilities->CallLogPop(1033);
6220  return;
6221  }
6222  // here when other train is adjacent & 30 secs elapsed since both adjacent
6223 
6224  // set new values for mass etc
6225  if(MaxRunningSpeed > TrainToBeJoinedBy->MaxRunningSpeed)
6226  {
6227  MaxRunningSpeed = TrainToBeJoinedBy->MaxRunningSpeed;
6228  }
6229  double OtherBrakeForce = TrainToBeJoinedBy->MaxBrakeRate * TrainToBeJoinedBy->Mass;
6230  double OwnBrakeForce = MaxBrakeRate * Mass;
6231  double CombinedBrakeRate = (OtherBrakeForce + OwnBrakeForce) / (TrainToBeJoinedBy->Mass + Mass);
6232 
6233  Mass += TrainToBeJoinedBy->Mass;
6234  MaxBrakeRate = CombinedBrakeRate;
6235  PowerAtRail += TrainToBeJoinedBy->PowerAtRail;
6236  AValue = sqrt(2 * PowerAtRail / Mass);
6237 
6239  TrainToBeJoinedBy->TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
6240  TrainToBeJoinedBy->TimetableFinished = true;
6241  TrainToBeJoinedBy->TrainGone = true;
6242  TrainController->LogEvent("" + AnsiString(Caller) + "," + HeadCode + ",Joined By," + FJOHeadCode); //added at v2.13.2 to provide more information
6243  // this will cause other train to be deleted
6244  TrainToBeJoinedBy->JoinedOtherTrainFlag = true;
6248  Utilities->CallLogPop(1034);
6249 }
6250 
6251 // ---------------------------------------------------------------------------
6252 
6253 void TTrain::ChangeTrainDirection(int Caller, bool NoLogFlag)
6254 {
6255  TrainController->LogEvent("" + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6256  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ChangeTrainDirection" + "," + HeadCode);
6257 
6258  /* restriction removed at v2.19.0
6259  if(PowerAtRail < 1)
6260  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can change direction when power restored)
6261  {
6262  if(!ZeroPowerNoCDTMessage)
6263  {
6264  TrainController->StopTTClockMessage(86, HeadCode + ": A train without power can't change direction under timetable control");
6265  }
6266  ZeroPowerNoCDTMessage = true;
6267  Utilities->CallLogPop(2141);
6268  return;
6269  }
6270 */
6271  TColor TempColour = BackgroundColour;
6272 
6273  UnplotTrain(2);
6276  StartSpeed = 0;
6277  StoppedAtLocation = true;
6278  PlotStartPosition(1);
6279  PlotTrainWithNewBackgroundColour(27, TempColour, Display);
6280  // plot same as was - should always be pale green
6281  if(!NoLogFlag)
6282  {
6285  }
6287 
6288  //now erase a stub route if there is one, added at v2.5.1
6289  //first element of route is now immediately behind the train (i.e. next to MidElement)
6290  if(MidEntryPos >= 0)
6291  {
6292  TTrackElement MidTrackElement = Track->TrackElementAt(996, MidElement);
6293  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
6294  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
6295  int RouteNumber = -1;
6296  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(34, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
6297  if(RouteType == TAllRoutes::NotAutoSigsRoute)
6298  {
6299  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(28, RouteNumber);
6300  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
6301  //elements can continue to be removed from that route
6302  TTrackElement TE = Track->TrackElementAt(997, FirstRouteElementVecPos);
6303 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
6304  { //above condition removed v2.17.0 so non-facing signal or continuation doesn't stop route being removed
6305  //if it is a facing signal then it will be detected below and not removed
6306  bool FirstPass = true; //added at v2.8.0
6307  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
6308  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
6309  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(247, 0); //these will change at each element removal because OR is a reference to the real route
6310  int TVPos2 = PDE.GetTrackVectorPosition();
6311  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
6312  {
6313  break;
6314  }
6315  TTrackElement TE2 = Track->TrackElementAt(998, TVPos2);
6317  {
6318  AllRoutes->RemoveRouteElement(22, TE2.HLoc, TE2.VLoc, PDE.GetELink());
6319  }
6320  else
6321  {
6322  break;
6323  }
6324  FirstPass = false;
6325  }
6326  AllRoutes->RebuildRailwayFlag = true;
6327  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
6328  }
6329  }
6330  }
6331  Utilities->CallLogPop(1012);
6332 }
6333 
6334 // ---------------------------------------------------------------------------
6335 
6336 void TTrain::NewTrainService(int Caller, bool NoLogFlag) //, bool NoLogFlag added at v2.12.0 for new service tt skips
6337 // change to new train, give new service message
6338 //same RepeatNumber used for the new service
6339 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6340  TrainController->LogEvent("" + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6341  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewTrainService" + "," + HeadCode);
6342 
6343 /* restriction removed at v2.19.0
6344  if(PowerAtRail < 1)
6345  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can form new service when power restored)
6346  {
6347  if(!ZeroPowerNoNewServiceMessage)
6348  {
6349  TrainController->StopTTClockMessage(87, HeadCode + ": A train without power can't form a new service");
6350  }
6351  ZeroPowerNoNewServiceMessage = true;
6352  Utilities->CallLogPop(2142);
6353  return;
6354  }
6355 */
6357 
6358  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6359 
6360  if(!NoLogFlag)
6361  {
6363  }
6364  UnplotTrain(3);
6367  StartSpeed = 0;
6372  HeadCode = NewHeadCode;
6374  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6375  {
6376  Description = OriginalDescription; //changed at v2.16.1 to train description
6377  }
6378  StoppedAtLocation = true;
6379  PlotStartPosition(5);
6381  // pale green
6384  TerminatedMessageSent = false;
6385  Utilities->CallLogPop(1022);
6386 }
6387 
6388 // ---------------------------------------------------------------------------
6389 
6390 void TTrain::RemainHere(int Caller) //added warnings & reminders at v2.19.0 (not sent from LogAction as not called when train terminates)
6391 {
6392  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6393  if(RemainHereLogNotSent) // to prevent repeated logs
6394  {
6395  TrainController->LogEvent(Utilities->TimeStamp() + AnsiString(Caller) + ",RemainHere" + "," + HeadCode);
6396  RemainHereLogNotSent = false;
6397  }
6399  {
6400  TDateTime ActualTime = TrainController->TTClockTime;
6401  AnsiString BaseLog = "", Location = ActionVectorEntryPtr->LocationName;
6402  AnsiString PerfLog = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode + " terminated at " + Location;
6404  {
6405  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " WARNING: " + HeadCode + " terminated at " + Location;
6406  Display->WarningLog(25, PerfLog);
6407  PerfLogForm->PerformanceLog(65, BaseLog);
6408  }
6409  else if(ActionVectorEntryPtr->Reminder > 0)
6410  {
6411  BaseLog = Utilities->Format96HHMMSS(ActualTime) + " REMINDER: " + HeadCode + " terminated at " + Location;
6412  Display->WarningLog(26, PerfLog);
6413  PerfLogForm->PerformanceLog(66, BaseLog);
6414  }
6415  else
6416  {
6417  PerfLogForm->PerformanceLog(67, PerfLog);
6418  }
6420  TerminatedMessageSent = true;
6421  }
6422  TimetableFinished = true;
6423  Utilities->CallLogPop(1023);
6424 }
6425 
6426 // ---------------------------------------------------------------------------
6427 
6428 void TTrain::SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
6429 /*
6430  Enter with pointer at next expected action, and IncNum the number by which have to increase the pointer
6431  to reach the action that is valid for the train's current position. i.e. IncNum error messages to be sent
6432  except where an action is a departure, starting at the current value for the pointer
6433  If IncNum is -1, then send messages for all remaining actions, including Fer if present
6434  If IncNum is -2, then send messages for all remaining actions, except Fer if present
6435 */{
6436  if((Ptr->Command == "Snt") && Ptr->SignallerControl)
6437  {
6438  return; // if remove train that starts under signaller control no messages needed
6439 
6440  }
6441  TrainController->LogEvent("" + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6442  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendMissedActionLogs," + AnsiString(IncNum) + "," + HeadCode);
6443  if(IncNum > 0)
6444  {
6445  for(int x = 0; x < IncNum; x++)
6446  {
6447  if(x > 0)
6448  {
6449  Ptr++;
6450  }
6451  // arrival - no need to test for termination as this section only covers missed actions up to the
6452  // arrival point - may terminate later but that not missed
6453  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6454  {
6456  }
6457  // arrival & departure
6458  if(Ptr->FormatType == TimeTimeLoc)
6459  {
6461  }
6462  // departure
6463  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6464  {
6465  continue; // skip TimeLoc departures, message given for arrivals
6466  }
6467  // pass
6468  else if(Ptr->FormatType == PassTime)
6469  {
6471  }
6472  // split
6473  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6474  {
6476  }
6477  // jbo
6478  else if(Ptr->Command == "jbo")
6479  {
6481  }
6482  // dsc
6483  else if(Ptr->Command == "dsc") //new at v2.15.0
6484  {
6486  }
6487  // Errors - have reached a station stop point (before a cdt) during Train->Update() so intervening actions can't
6488  // be starts, finishes or cdt
6489  else if((Ptr->Command == "Fns") || (Ptr->Command == "Frh") || (Ptr->Command == "Fer") || (Ptr->Command == "Fjo") || (Ptr->Command == "Snt") ||
6490  (Ptr->Command == "Sfs") || (Ptr->Command == "Snt-sh") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") || (Ptr->Command == "Sns-fsh") ||
6491  (Ptr->Command == "cdt") || (Ptr->Command == "Frh-sh") || (Ptr->Command == "Fns-sh") || (Ptr->Command == "F-nshs") ||
6492  (Ptr->FormatType == Repeat))
6493  {
6494  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6495  }
6496  }
6497  }
6498  else
6499  {
6500  bool IncludeFER = false;
6501  if(IncNum == -1)
6502  {
6503  IncludeFER = true;
6504  }
6505  while(true) // finish commands & repeats break out of loop
6506  {
6507  // Fer & excluded - send normal exit log to give minutes late or early - no, have already sent an unexpected exit message
6508  if(!IncludeFER && (Ptr->Command == "Fer"))
6509  {
6510  break;
6511  }
6512  // Fer & included
6513  else if(IncludeFER && (Ptr->Command == "Fer"))
6514  {
6516  break;
6517  }
6518  // Repeat
6519  else if(Ptr->FormatType == Repeat)
6520  {
6521  break;
6522  }
6523  // Fjo
6524  else if(Ptr->Command == "Fjo")
6525  {
6527  break;
6528  }
6529  // Frh
6530  else if(Ptr->Command == "Frh")
6531  {
6533  {
6535  TerminatedMessageSent = true;
6536  }
6537  break;
6538  }
6539  // Frh-sh
6540  else if(Ptr->Command == "Frh-sh")
6541  {
6543  {
6545  TerminatedMessageSent = true;
6546  }
6547  break;
6548  }
6549  // Fns, F-nshs, Fns-sh
6550  else if((Ptr->Command == "Fns") || (Ptr->Command == "F-nshs") || (Ptr->Command == "Fns-sh"))
6551  {
6553  break;
6554  }
6555  // end of breakout actions
6556  // arrival
6557  if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime > TDateTime(-1)))
6558  {
6559  if(IsTrainTerminating(1))
6560  {
6562  TerminatedMessageSent = true;
6563  }
6564  else
6565  {
6567  }
6568  }
6569  // arrival & departure
6570  else if(Ptr->FormatType == TimeTimeLoc)
6571  {
6573  }
6574  // departure
6575  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
6576  {
6577  Ptr++;
6578  continue; // skip TimeLoc departures, message given for arrivals
6579  }
6580  // pass
6581  else if(Ptr->FormatType == PassTime)
6582  {
6584  }
6585  // split
6586  else if((Ptr->Command == "fsp") || (Ptr->Command == "rsp"))
6587  {
6589  }
6590  // jbo
6591  else if(Ptr->Command == "jbo")
6592  {
6594  }
6595  // dsc
6596  else if(Ptr->Command == "dsc") //new at v2.15.0
6597  {
6598 // TrainController->LogActionError(65, HeadCode, "", FailMissedDSC, Ptr->LocationName); don't count as a missed event
6599  }
6600  // cdt
6601  else if(Ptr->Command == "cdt")
6602  {
6603 // TrainController->LogActionError(25, HeadCode, "", FailMissedChangeDirection, Ptr->LocationName); //commented out at v2.12.0 as cdts not counted as missed events
6604  }
6605  // Errors
6606  else if((Ptr->Command == "Snt-sh") || (Ptr->Command == "Sfs") || (Ptr->Command == "Sns") || (Ptr->Command == "Sns-sh") ||
6607  (Ptr->Command == "Sns-fsh") || ((Ptr->Command == "Snt") && !Ptr->SignallerControl))
6608  {
6609  throw Exception("Error - illegal command in SendMissedActionLogs for IncNum = " + AnsiString(IncNum) + ", and command = " + Ptr->Command);
6610  }
6611  Ptr++;
6612  }
6613  TimetableFinished = true;
6614  }
6615  Utilities->CallLogPop(1021);
6616 }
6617 
6618 // ---------------------------------------------------------------------------
6619 
6620 bool TTrain::TrainToJoinIsAdjacent(int Caller, TTrain* &TrainToJoin)
6621 // ensure same repeatnumber
6622 {
6623  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToJoinIsAdjacent" + "," + HeadCode);
6625 
6626  if(TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
6627  {
6628  Utilities->CallLogPop(1024);
6629  return(false);
6630  }
6631  TrainToJoin = &(TrainController->TrainVectorAtIdent(33, TrainToJoinTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
6632  if(TrainToJoin->StoppedAtLocation && (TrainToJoin->TrainMode == Timetable) && (TrainToJoin->ActionVectorEntryPtr->Command == "jbo"))
6633  {
6634  if((Track->TrackElementAt(610, LeadElement).Conn[LeadExitPos] == TrainToJoin->LeadElement) || (Track->TrackElementAt(611,
6635  LeadElement).Conn[LeadExitPos] == TrainToJoin->MidElement) || (Track->TrackElementAt(612, MidElement).Conn[MidEntryPos] == TrainToJoin->LeadElement)
6636  || (Track->TrackElementAt(613, MidElement).Conn[MidEntryPos] == TrainToJoin->MidElement))
6637  {
6638  Utilities->CallLogPop(1025);
6639  return(true);
6640  }
6641  }
6642  Utilities->CallLogPop(1026);
6643  return(false);
6644 }
6645 
6646 // ---------------------------------------------------------------------------
6647 
6648 bool TTrain::TrainToBeJoinedByIsAdjacent(int Caller, TTrain* &TrainToBeJoinedBy)
6649 // ensure same repeatnumber
6650 {
6651  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainToBeJoinedByIsAdjacent" + "," + HeadCode);
6652  TTrainDataEntry *TrainToBeJoinedByTDEntry = ActionVectorEntryPtr->LinkedTrainEntryPtr;
6653 
6654  if(TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).RunningEntry != Running)
6655  {
6656  Utilities->CallLogPop(1027);
6657  return(false);
6658  }
6659  TrainToBeJoinedBy = &(TrainController->TrainVectorAtIdent(15, TrainToBeJoinedByTDEntry->TrainOperatingDataVector.at(RepeatNumber).TrainID));
6660  if(TrainToBeJoinedBy->StoppedAtLocation && (TrainToBeJoinedBy->TrainMode == Timetable) && (TrainToBeJoinedBy->ActionVectorEntryPtr->Command == "Fjo"))
6661  {
6662  if((Track->TrackElementAt(614, LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(615,
6663  LeadElement).Conn[LeadExitPos] == TrainToBeJoinedBy->MidElement) || (Track->TrackElementAt(616,
6664  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->LeadElement) || (Track->TrackElementAt(617,
6665  MidElement).Conn[MidEntryPos] == TrainToBeJoinedBy->MidElement))
6666  {
6667  Utilities->CallLogPop(1028);
6668  return(true);
6669  }
6670  }
6671  Utilities->CallLogPop(1029);
6672  return(false);
6673 }
6674 
6675 // ---------------------------------------------------------------------------
6676 
6677 void TTrain::NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
6678 { //same RepeatNumber (i.e. 0) used for the new shuttle
6679 //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6680  TrainController->LogEvent("" + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
6681  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",NewShuttleFromNonRepeatService" + "," + HeadCode);
6682 
6683 /* restriction removed at v2.19.0
6684  if(PowerAtRail < 1)
6685  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
6686  {
6687  if(!ZeroPowerNoNewShuttleFromNonRepeatMessage)
6688  {
6689  TrainController->StopTTClockMessage(88, HeadCode + ": A train without power can't form a new service");
6690  }
6691  ZeroPowerNoNewShuttleFromNonRepeatMessage = true;
6692  Utilities->CallLogPop(2143);
6693  return;
6694  }
6695 */
6696  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
6697  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6698 
6699  if(!NoLogFlag)
6700  {
6702  }
6703  UnplotTrain(4);
6706  StartSpeed = 0;
6711  HeadCode = NewHeadCode;
6713  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6714  {
6715  Description = OriginalDescription; //changed at v2.16.1 to train description
6716  }
6717  IncrementalMinutes = TrainDataEntryPtr->ActionVector.back().RearStartOrRepeatMins;
6718  IncrementalDigits = TrainDataEntryPtr->ActionVector.back().FrontStartOrRepeatDigits;
6719  StoppedAtLocation = true;
6720  PlotStartPosition(6);
6722  // pale green
6725  TerminatedMessageSent = false;
6726  Utilities->CallLogPop(1078);
6727 }
6728 
6729 // ---------------------------------------------------------------------------
6730 
6731 void TTrain::RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips)
6732 // need to check whether all repeats finished or not
6733 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6734  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
6735  if(RemainHereLogNotSent) // to prevent repeated logs
6736  {
6737  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrRemainHere" + "," + HeadCode);
6738  RemainHereLogNotSent = false;
6739  }
6741  // finished all repeats
6742  {
6744  { //no need to suppress this LogAction because BecomeNewService won't be available in this case
6747  TerminatedMessageSent = true;
6748  // no need to clear message as no more actions
6749  }
6750  TimetableFinished = true;
6751  Utilities->CallLogPop(1080);
6752  return;
6753  }
6754 
6755 /* restriction removed at v2.19.0
6756  if(PowerAtRail < 1)
6757  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
6758  {
6759  if(!ZeroPowerNoRepeatShuttleMessage)
6760  {
6761  TrainController->StopTTClockMessage(89, HeadCode + ": A train without power can't form a new service");
6762  }
6763  ZeroPowerNoRepeatShuttleMessage = true;
6764  Utilities->CallLogPop(2144);
6765  return;
6766  }
6767 */
6768  int TempRepeatNumber = RepeatNumber + 1;
6769  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
6770  // until after LogAction or the wrong time will be used
6771  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(6, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
6772  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6773 
6774  if(!NoLogFlag)
6775  {
6777  }
6778  RepeatNumber++;
6779  UnplotTrain(5);
6782  StartSpeed = 0;
6787  HeadCode = NewHeadCode;
6789  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6790  {
6791  Description = OriginalDescription; //changed at v2.16.1 to train description
6792  }
6793  StoppedAtLocation = true;
6794  PlotStartPosition(7);
6796  // pale green
6799  TerminatedMessageSent = false;
6800  Utilities->CallLogPop(1079);
6801 }
6802 
6803 // ---------------------------------------------------------------------------
6804 
6805 void TTrain::RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag) //bool NoLogFlag added at v2.12.0 for new service TT skips
6806 { //Note that CumulativeDelayedRandMinsOneTrain carried forward from earlier train (parameter added at v2.13.0)
6807  TrainController->LogEvent("" + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
6808  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RepeatShuttleOrNewNonRepeatService" + "," + HeadCode);
6809 
6810 /* restriction removed at v2.19.0
6811  if(PowerAtRail < 1)
6812  // new at v2.4.0 (ActionVectorEntryPtr not incremented so can for new service when power restored)
6813  {
6814  if(!ZeroPowerNoRepeatShuttleOrNewServiceMessage)
6815  {
6816  TrainController->StopTTClockMessage(90, HeadCode + ": A train without power can't form a new service");
6817  }
6818  ZeroPowerNoRepeatShuttleOrNewServiceMessage = true;
6819  Utilities->CallLogPop(2145);
6820  return;
6821  }
6822 */
6824  // finished all repeats
6825  {
6826  AnsiString NewHeadCode = ActionVectorEntryPtr->NonRepeatingShuttleLinkHeadCode;
6827  if(!NoLogFlag)
6828  {
6830  }
6831  RepeatNumber = 0;
6832  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6833  UnplotTrain(6);
6836  StartSpeed = 0;
6838  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).TrainID = TrainID; // but note that RepeatNumber = 0
6839  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).RunningEntry = Running; // but note that RepeatNumber = 0
6841  HeadCode = NewHeadCode;
6843  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6844  {
6845  Description = OriginalDescription; //changed at v2.16.1 to train description
6846  }
6847  StoppedAtLocation = true;
6848  PlotStartPosition(9);
6852  TerminatedMessageSent = false;
6853  Utilities->CallLogPop(1081);
6854  return;
6855  }
6856  int TempRepeatNumber = RepeatNumber + 1;
6857  // need the next repeat value in order to obtain a correct NewHeadCode, but don't increase it
6858  // until after LogAction or the wrong time will be used
6859  AnsiString NewHeadCode = TrainController->GetRepeatHeadCode(7, ActionVectorEntryPtr->OtherHeadCode, TempRepeatNumber, IncrementalDigits);
6860  AnsiString OriginalDescription = Description; //new at v2.15.0 to record earlier service description & changed at v2.16.1 to train description
6861 
6862  if(!NoLogFlag)
6863  {
6865  }
6866  RepeatNumber++;
6867  UnplotTrain(7);
6870  StartSpeed = 0;
6875  HeadCode = NewHeadCode;
6877  if(!TrainDataEntryPtr->ExplicitDescription) //new at v2.15.0 see above
6878  {
6879  Description = OriginalDescription; //changed at v2.16.1 to train description
6880  }
6881  StoppedAtLocation = true;
6882  PlotStartPosition(8);
6884  // pale green
6887  TerminatedMessageSent = false;
6888  Utilities->CallLogPop(1082);
6889 }
6890 
6891 // ---------------------------------------------------------------------------
6892 
6894 {
6895  // Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
6896  // entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
6897  // must be preceded by a TimeLoc departure
6898  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsTrainTerminating" + "," + HeadCode);
6899  for(unsigned int x = 1; x < TrainDataEntryPtr->ActionVector.size(); x++)
6900  {
6902  {
6903  if(((ActionVectorEntryPtr + x)->Command == "Fer") || ((ActionVectorEntryPtr + x)->FormatType == TimeLoc))
6904  {
6905  Utilities->CallLogPop(1083);
6906  return(false);
6907  }
6908  else if((ActionVectorEntryPtr + x)->SequenceType == FinishSequence)
6909  {
6910  Utilities->CallLogPop(1084);
6911  return(true);
6912  }
6913  }
6914  }
6915  Utilities->CallLogPop(1085);
6916  return(false);
6917 }
6918 
6919 // ---------------------------------------------------------------------------
6920 
6921 bool TTrain::AbleToMove(int Caller)
6922 {
6923  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMove" + "," + HeadCode);
6924  bool Able = true;
6925 
6926  if(Crashed || Derailed || StoppedAtBuffers || StoppedAtSignal || StoppedWithoutPower) // StoppedWithoutPower added at v2.4.0
6927  {
6928  // StoppedForTrainInFront removed as tested below
6929  Utilities->CallLogPop(2146); // added v2.4.0
6930  return(false); // added v2.4.0
6931  }
6932  if(LeadElement > -1)
6933  {
6934  if(Track->TrackElementAt(801, LeadElement).TrackType == Buffers) //moved up here from 'else' below at v2.12.0
6935  {
6936  StoppedForTrainInFront = false;
6937  TrainInFront = false;
6938  // don't set StoppedAtBuffers as (presumably) StoppedAtLocation & leave it at that
6939  Utilities->CallLogPop(2456);
6940  return(false);
6941  }
6942  int FrontPos = Track->TrackElementAt(678, LeadElement).Conn[LeadExitPos];
6943  int FrontEntryPos = Track->TrackElementAt(679, LeadElement).ConnLinkPos[LeadExitPos];
6944  if((FrontPos > -1) && (TrainMode == Signaller) && TrainInFront) //check if train in front still there
6945  {
6946  TTrackElement TrackElement = Track->TrackElementAt(680, FrontPos);
6947  if((TrackElement.TrackType != Bridge) && (TrackElement.TrainIDOnElement == -1))
6948  {
6949  Able = true;
6950  TrainInFront = false;
6951  }
6952  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos < 2) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 == -1))
6953  {
6954  Able = true;
6955  TrainInFront = false;
6956  }
6957  else if((TrackElement.TrackType == Bridge) && (FrontEntryPos > 1) && (TrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 == -1))
6958  {
6959  Able = true;
6960  TrainInFront = false;
6961  }
6962  else
6963  {
6964  Able = false; //added at v2.12.0 as train still in front so don't want signaller popup options to move available
6965  }
6966  }
6967  }
6968  else // leaving at a continuation so keep going
6969  {
6970  Able = true;
6971  StoppedForTrainInFront = false;
6972  TrainInFront = false;
6973  }
6974  Utilities->CallLogPop(1454);
6975  return(Able);
6976 }
6977 
6978 // ---------------------------------------------------------------------------
6979 
6981 {
6982  // first check if a train immediately in front (may have moved there since this train stopped so StoppedForTrainInFront
6983  // won't be set; if there is a train then set StoppedForTrainInFront
6984  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AbleToMoveButForSignalOrTrainInFront" + "," + HeadCode);
6985  // addition below for v1.3.2 after Carwyn Thomas fault reported 24/05/15 - need to check if exiting at continuation (LeadElement == -1) as if so fails at VecPos = .....
6986  if(LeadElement == -1) // exiting at continuation
6987  {
6988  Utilities->CallLogPop(2045);
6989  return(false);
6990  }
6991  // end of addition
6992  int VecPos = Track->TrackElementAt(654, LeadElement).Conn[LeadExitPos];
6993  int NextEntryPos = Track->TrackElementAt(655, LeadElement).ConnLinkPos[LeadExitPos];
6994 
6995  if(Track->OtherTrainOnTrack(5, VecPos, NextEntryPos, TrainID))
6996  {
6997  TrainInFront = true;
6998  Utilities->CallLogPop(1455);
6999  return(false);
7000  }
7001  else
7002  {
7003  Utilities->CallLogPop(1456);
7005  // StoppedWithoutPower added v2.4.0
7006  }
7007 }
7008 
7009 // ---------------------------------------------------------------------------
7010 
7012 {
7013  // unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd
7014  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SignallerChangeTrainDirection" + "," + HeadCode);
7015  TColor TempColour = BackgroundColour;
7016 
7017  UnplotTrain(8);
7020  StartSpeed = 0;
7021  PlotStartPosition(2);
7022  PlotTrainWithNewBackgroundColour(26, TempColour, Display);
7023 
7024  //now erase a stub route if there is one, added at v2.5.1
7025  //first element of route is now immediately behind the train (i.e. next to MidElement)
7026  if(MidEntryPos >= 0)
7027  {
7028  TTrackElement MidTrackElement = Track->TrackElementAt(1000, MidElement);
7029  int FirstRouteElementVecPos = MidTrackElement.Conn[MidEntryPos];
7030  int FirstRouteLinkPos = MidTrackElement.ConnLinkPos[MidEntryPos];
7031  int RouteNumber = -1;
7032  TAllRoutes::TRouteType RouteType = AllRoutes->GetRouteTypeAndNumber(35, FirstRouteElementVecPos, FirstRouteLinkPos, RouteNumber);
7033  if(RouteType == TAllRoutes::NotAutoSigsRoute)
7034  {
7035  TOneRoute &OR = AllRoutes->GetModifiableRouteAt(29, RouteNumber);
7036  int CorrectRouteID = OR.RouteID; //added at v2.13.0 as when last element removed & route removed from vector OR becomes the next route after the erased one and
7037  //elements can continue to be removed from that route
7038  TTrackElement TE = Track->TrackElementAt(1001, FirstRouteElementVecPos);
7039 // if((TE.TrackType != SignalPost) && (TE.TrackType != Continuation)) //all autosigs routes have signalpost or continuation at 0 so they are automatically excluded
7040  { //above condition removed at v2.17.0 so non-facing signal or continuation doesn't stop route being removed
7041  //if it is a facing signal then it will be detected below and not removed
7042  bool FirstPass = true; //added at v2.8.0
7043  while((OR.PrefDirSize() > 0) && (OR.RouteID == CorrectRouteID)) //remove the route up to but not including the next facing signal, in case a pref dir route extends to another signal
7044  { // && (OR.RouteID == RouteID) added at v2.13.0 to prevent another route having elements removed
7045  TPrefDirElement PDE = OR.GetFixedPrefDirElementAt(248, 0); //these will change at each element removal because OR is a reference to the real route
7046  int TVPos2 = PDE.GetTrackVectorPosition();
7047  if(FirstPass && (TVPos2 != FirstRouteElementVecPos)) //route is not directed away from cdt train, could be a call-on for another train (added at v2.8.0)
7048  {
7049  break;
7050  }
7051  TTrackElement TE2 = Track->TrackElementAt(1002, TVPos2);
7053  {
7054  AllRoutes->RemoveRouteElement(23, TE2.HLoc, TE2.VLoc, PDE.GetELink());
7055  }
7056  else
7057  {
7058  break;
7059  }
7060  FirstPass = false;
7061  }
7062  AllRoutes->RebuildRailwayFlag = true;
7063  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot without stub route
7064  }
7065  }
7066  }
7067  Utilities->CallLogPop(1102);
7068 }
7069 
7070 // ---------------------------------------------------------------------------
7071 
7073 {
7074  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7075  ",FloatingLabelNextString" + "," + HeadCode);
7076  AnsiString RetStr = "", LocationName = "";
7077  //record action time - may be arrival, departure or event for use later (added at v2.13.2)
7078  TDateTime ActionTime = Ptr->ArrivalTime;
7079  if(ActionTime == TDateTime(-1))
7080  {
7081  ActionTime = Ptr->DepartureTime;
7082  }
7083  if(ActionTime == TDateTime(-1))
7084  {
7085  ActionTime = Ptr->EventTime;
7086  }
7087  //If ActionTime still TDateTime(-1) then the train has terminated and 'None...' will be returned
7088  //Now correct it for repeats
7089  if(ActionTime != TDateTime(-1))
7090  {
7091  ActionTime = GetTrainTime(64, ActionTime);
7092  }
7093  if(int(DelayedRandMins) > 0)
7094  {
7095  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7096  {
7097  throw Exception("Error - start entry in FloatingLabelNextString");
7098  }
7099  if(Ptr->FormatType == TimeTimeLoc)
7100  {
7101  if(TrainMode == Timetable)
7102  {
7103  if(!TrainAtLocation(0, LocationName) || (LocationName != Ptr->LocationName))
7104  // not arrived yet in tt mode
7105  {
7106  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(2, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7107  }
7108  else
7109  {
7110  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7111  }
7112  }
7113  else // TrainMode == Signaller
7114  {
7115  if(!DepartureTimeSet) // not arrived yet
7116  {
7117  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(45, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7118  }
7119  else
7120  {
7121  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7122  }
7123  }
7124  }
7125  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7126  {
7127  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(4, Ptr->ArrivalTime + TDateTime(DelayedRandMins/1440)));
7128  }
7129  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7130  {
7131  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7132  }
7133  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7134  {
7135  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(46, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7136  }
7137  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7138  {
7139  RetStr = "Pass " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(31, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7140  }
7141  else if(Ptr->Command == "Fns")
7142  {
7143  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(8, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7144  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(6, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7145  RetStr = GetNewServiceDepartureInfo(0, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7146  }
7147  else if(Ptr->Command == "F-nshs")
7148  {
7149  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7150  Utilities->Format96HHMM(GetTrainTime(32, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7151  RetStr = GetNewServiceDepartureInfo(1, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7152  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7153  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7154  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7155  }
7156  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7157  {
7158  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(9, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7159  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(7, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7160  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7161  RetStr = GetNewServiceDepartureInfo(2, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7162  }
7163  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7164  {
7165  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7166  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(8, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7167  RetStr = GetNewServiceDepartureInfo(3, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7168  }
7169  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7170  {
7171  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(10, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7172  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(9, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7173  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7174  RetStr = GetNewServiceDepartureInfo(4, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7175  }
7176  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7177  {
7178  RetStr ="None, train terminated at " + Ptr->LocationName;
7179  }
7180  else if(Ptr->Command == "Frh")
7181  {
7182  RetStr = "None, train terminated at " + Ptr->LocationName;
7183  }
7184  else if(Ptr->Command == "Fer")
7185  {
7186  AnsiString AllowedExits = "";
7187  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(1, Ptr->ExitList, AllowedExits) + " at approx. " + Utilities->Format96HHMM(GetTrainTime(10, Ptr->EventTime + TDateTime(DelayedRandMins/1440))) + AllowedExits;
7188  }
7189  else if(Ptr->Command == "Fjo")
7190  {
7191  RetStr = "Join " + TrainController->GetRepeatHeadCode(11, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at approx. " +
7192  Utilities->Format96HHMM(GetTrainTime(11, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7193  }
7194  else if(Ptr->Command == "jbo")
7195  {
7196  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(12, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7197  " at approx. " + Utilities->Format96HHMM(GetTrainTime(12, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7198  }
7199  else if(Ptr->Command == "fsp")
7200  {
7201  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(13, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7202  " at approx. " + Utilities->Format96HHMM(GetTrainTime(13, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7203  }
7204  else if(Ptr->Command == "rsp")
7205  {
7206  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(14, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7207  " at approx. " + Utilities->Format96HHMM(GetTrainTime(14, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7208  }
7209  else if(Ptr->Command == "cdt")
7210  {
7211  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(15, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7212  }
7213  else if(Ptr->Command == "dsc")
7214  {
7215  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(65, Ptr->EventTime + TDateTime(DelayedRandMins/1440)));
7216  }
7217  }
7218  else if(TrainController->TTClockTime > ActionTime) //condition added at v2.13.2 for trains that are delayed other than suffering a random delay
7219  {
7220  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7221  {
7222  throw Exception("Error - start entry in FloatingLabelNextString where TTClockTime > ActionTime");
7223  }
7224  if(Ptr->FormatType == TimeTimeLoc)
7225  {
7226  if(TrainMode == Timetable)
7227  {
7228  if(!TrainAtLocation(4, LocationName) || (LocationName != Ptr->LocationName))
7229  // not arrived yet in tt mode
7230  {
7231  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7232  }
7233  else
7234  {
7235  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7236  }
7237  }
7238  else // TrainMode == Signaller
7239  {
7240  if(!DepartureTimeSet) // not arrived yet
7241  {
7242  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7243  }
7244  else
7245  {
7246  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7247  }
7248  }
7249  }
7250  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7251  {
7252  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7253  }
7254  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7255  {
7256  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);
7257  }
7258  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7259  {
7260  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7261  }
7262  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7263  {
7264  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7265  }
7266  else if(Ptr->Command == "Fns")
7267  {
7268  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(60, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7269  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7270  RetStr = GetNewServiceDepartureInfo(19, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7271  }
7272  else if(Ptr->Command == "F-nshs")
7273  {
7274  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7276  RetStr = GetNewServiceDepartureInfo(20, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7277  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7278  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7279  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7280  }
7281  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7282  {
7283  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(61, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7284  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7285  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7286  RetStr = GetNewServiceDepartureInfo(21, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7287  }
7288  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7289  {
7290  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7291  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7292  RetStr = GetNewServiceDepartureInfo(22, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7293  }
7294  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7295  {
7296  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(62, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7297  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7298  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7299  RetStr = GetNewServiceDepartureInfo(23, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7300  }
7301  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7302  {
7303  RetStr ="None, train terminated at " + Ptr->LocationName;
7304  }
7305  else if(Ptr->Command == "Frh")
7306  {
7307  RetStr = "None, train terminated at " + Ptr->LocationName;
7308  }
7309  else if(Ptr->Command == "Fer")
7310  {
7311  AnsiString AllowedExits = "";
7312  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(5, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime)*/ + AllowedExits;
7313  }
7314  else if(Ptr->Command == "Fjo")
7315  {
7316  RetStr = "Join " + TrainController->GetRepeatHeadCode(63, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7317 // Utilities->Format96HHMM(TrainController->TTClockTime);
7318  }
7319  else if(Ptr->Command == "jbo")
7320  {
7321  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(64, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7322 // " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7323  }
7324  else if(Ptr->Command == "fsp")
7325  {
7326  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(65, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7327  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7328  }
7329  else if(Ptr->Command == "rsp")
7330  {
7331  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(66, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7332  " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7333  }
7334  else if(Ptr->Command == "cdt")
7335  {
7336  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7337  }
7338  else if(Ptr->Command == "dsc")
7339  {
7340  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(TrainController->TTClockTime);
7341  }
7342  }
7343  else //train not delayed
7344  {
7345  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7346  {
7347  throw Exception("Error - start entry in FloatingLabelNextString in final 'else'");
7348  }
7349  if(Ptr->FormatType == TimeTimeLoc)
7350  {
7351  if(TrainMode == Timetable)
7352  {
7353  if(!TrainAtLocation(3, LocationName) || (LocationName != Ptr->LocationName))
7354  // not arrived yet in tt mode
7355  {
7356  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(48, Ptr->ArrivalTime));
7357  }
7358  else
7359  {
7360  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(3, Ptr->DepartureTime));
7361  }
7362  }
7363  else // TrainMode == Signaller
7364  {
7365  if(!DepartureTimeSet) // not arrived yet
7366  {
7367  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(49, Ptr->ArrivalTime));
7368  }
7369  else
7370  {
7371  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(36, Ptr->DepartureTime));
7372  }
7373  }
7374  }
7375  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7376  {
7377  RetStr = "Arrive " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(50, Ptr->ArrivalTime));
7378  }
7379  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7380  {
7381  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(5, Ptr->DepartureTime));
7382  }
7383  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7384  {
7385  RetStr = "Depart " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(51, Ptr->EventTime));
7386  }
7387  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7388  {
7389  RetStr = "Pass " + Ptr->LocationName;// + " at approx. " + Utilities->Format96HHMM(GetTrainTime(52, Ptr->EventTime));
7390  }
7391  else if(Ptr->Command == "Fns")
7392  {
7393  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(53, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7394  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(53, Ptr->EventTime));
7395  RetStr = GetNewServiceDepartureInfo(10, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7396  }
7397  else if(Ptr->Command == "F-nshs")
7398  {
7399  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at approx. " +
7401  RetStr = GetNewServiceDepartureInfo(12, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7402  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7403  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7404  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7405  }
7406  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7407  {
7408  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(54, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7409  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(55, Ptr->EventTime));
7410  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7411  RetStr = GetNewServiceDepartureInfo(14, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7412  }
7413  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7414  {
7415  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7416  +" at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(56, Ptr->EventTime));
7417  RetStr = GetNewServiceDepartureInfo(16, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7418  }
7419  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7420  {
7421  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(55, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7422  Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(57, Ptr->EventTime));
7423  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7424  RetStr = GetNewServiceDepartureInfo(18, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr, false); //if there is a next service this adds the new service departure time to RetStr
7425  }
7426  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7427  {
7428  RetStr ="None, train terminated at " + Ptr->LocationName;
7429  }
7430  else if(Ptr->Command == "Frh")
7431  {
7432  RetStr = "None, train terminated at " + Ptr->LocationName;
7433  }
7434  else if(Ptr->Command == "Fer")
7435  {
7436  AnsiString AllowedExits = "";
7437  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(4, Ptr->ExitList, AllowedExits) /*+ " at approx. " + Utilities->Format96HHMM(GetTrainTime(62, Ptr->EventTime))*/ + AllowedExits;
7438  }
7439  else if(Ptr->Command == "Fjo")
7440  {
7441  RetStr = "Join " + TrainController->GetRepeatHeadCode(56, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// + " at approx. " +
7442 // Utilities->Format96HHMM(GetTrainTime(58, Ptr->EventTime));
7443  }
7444  else if(Ptr->Command == "jbo")
7445  {
7446  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(57, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;// +
7447 // " at approx. " + Utilities->Format96HHMM(GetTrainTime(59, Ptr->EventTime));
7448  }
7449  else if(Ptr->Command == "fsp")
7450  {
7451  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(58, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7452  " at approx. " + Utilities->Format96HHMM(GetTrainTime(60, Ptr->EventTime));
7453  }
7454  else if(Ptr->Command == "rsp")
7455  {
7456  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(59, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7457  " at approx. " + Utilities->Format96HHMM(GetTrainTime(61, Ptr->EventTime));
7458  }
7459  else if(Ptr->Command == "cdt")
7460  {
7461  RetStr = "Change direction at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(63, Ptr->EventTime));
7462  }
7463  else if(Ptr->Command == "dsc")
7464  {
7465  RetStr = "Change description at " + Ptr->LocationName + " at approx. " + Utilities->Format96HHMM(GetTrainTime(66, Ptr->EventTime));
7466  }
7467  }
7468  Utilities->CallLogPop(1124);
7469  return(RetStr);
7470 }
7471 
7472 // ---------------------------------------------------------------------------
7473 /* as was
7474 AnsiString TTrain::FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
7475 {
7476  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7477  ",FloatingLabelNextString" + "," + HeadCode);
7478  AnsiString RetStr = "", LocationName = "";
7479 
7480  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
7481  {
7482  throw Exception("Error - start entry in FloatingLabelNextString");
7483  }
7484  if(Ptr->FormatType == TimeTimeLoc)
7485  {
7486  if(TrainMode == Timetable)
7487  {
7488  if(!TrainAtLocation(, LocationName) || (LocationName != Ptr->LocationName))
7489  // not arrived yet in tt mode
7490  {
7491  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7492  }
7493  else
7494  {
7495  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7496  }
7497  }
7498  else // TrainMode == Signaller
7499  {
7500  if(!DepartureTimeSet) // not arrived yet
7501  {
7502  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7503  }
7504  else
7505  {
7506  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7507  }
7508  }
7509  }
7510  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7511  {
7512  RetStr = "Arrive " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->ArrivalTime));
7513  }
7514  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7515  {
7516  RetStr = "Depart " + Ptr->LocationName + " approx. " + Utilities->Format96HHMM(ReleaseTime);//GetTrainTime(, Ptr->DepartureTime));
7517  }
7518  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewservice)
7519  {
7520  RetStr = "Depart " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7521  }
7522  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7523  {
7524  RetStr = "Pass " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7525  }
7526  else if(Ptr->Command == "Fns")
7527  {
7528  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " +
7529  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7530  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7531  }
7532  else if(Ptr->Command == "F-nshs")
7533  {
7534  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName + " at " +
7535  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7536  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7537  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7538  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7539  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7540  }
7541  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7542  {
7543  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7544  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7545  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7546  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7547  }
7548  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7549  {
7550  RetStr = "Forms new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7551  +" at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7552  RetStr = GetNewServiceDepartureInfo(, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7553  }
7554  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not last repeat number
7555  {
7556  RetStr = "Forms new service " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " +
7557  Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7558  // use RepeatNumber+1 as it's the repeat number of the NEXT shuttle service that is relevant
7559  RetStr = GetNewServiceDepartureInfo(, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, RetStr); //if there is a next service this adds the new service departure time to RetStr
7560  }
7561  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7562  {
7563  RetStr ="None, train terminated at " + Ptr->LocationName;
7564  }
7565  else if(Ptr->Command == "Frh")
7566  {
7567  RetStr = "None, train terminated at " + Ptr->LocationName;
7568  }
7569  else if(Ptr->Command == "Fer")
7570  {
7571  AnsiString AllowedExits = "";
7572  RetStr = "Exit railway" + TrainController->GetExitLocationAndAt(, Ptr->ExitList, AllowedExits) + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime)) + AllowedExits;
7573  }
7574  else if(Ptr->Command == "Fjo")
7575  {
7576  RetStr = "Join " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName + " at " +
7577  Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7578  }
7579  else if(Ptr->Command == "jbo")
7580  {
7581  RetStr = "Joined by " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7582  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7583  }
7584  else if(Ptr->Command == "fsp")
7585  {
7586  RetStr = "Front split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7587  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7588  }
7589  else if(Ptr->Command == "rsp")
7590  {
7591  RetStr = "Rear split to " + TrainController->GetRepeatHeadCode(, Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName +
7592  " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7593  }
7594  else if(Ptr->Command == "cdt")
7595  {
7596  RetStr = "Change direction at " + Ptr->LocationName + " at " + Utilities->Format96HHMM(GetTrainTime(, Ptr->EventTime));
7597  }
7598  Utilities->CallLogPop();
7599  return(RetStr);
7600 }
7601 */
7602 // ---------------------------------------------------------------------------
7603 
7604 AnsiString TTrain::GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
7605 { //last bool added at v2.13.2 so departure info adds random delay if actual rather than not timetable time required
7606  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) + ","
7607  + AnsiString(RptNum) + ",GetNewServiceDepartureInfo," + HeadCode);
7608  AnsiString DepTime = "", EventTime = "";
7609  bool CDTFlag = false; //reports if train changes direction before departs
7610  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
7611  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
7612  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
7613  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
7614  {
7615  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
7616  {
7617  TowardsLocation = AVI->LocationName;
7618  }
7619  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
7620  {
7621  TTrackElement TE = Track->TrackElementAt(1452, (AVI->ExitList.front()));
7622  if(TE.ActiveTrackElementName != "")
7623  {
7624  TowardsLocation = TE.ActiveTrackElementName;
7625  }
7626  else
7627  {
7628  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
7629  }
7630  }
7631  }
7632 
7633  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
7634  {
7635  if(AVI->Command == "cdt")
7636  {
7637  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
7638  continue;
7639  }
7640  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
7641  {
7642  TDateTime TTTime = TrainController->GetControllerTrainTime(19, AVI->EventTime, RptNum, IncrementalMinutes);
7643  if((DelayedRandMins >= 1) && !TimetableTime)
7644  {
7645  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7646  }
7647  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7648  {
7650  }
7651  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7652  {
7653  EventTime = Utilities->Format96HHMM(TTTime);
7654  }
7655  RetStr += "\nNew service splits at approx. " + EventTime;
7656  Utilities->CallLogPop(2234);
7657  return(RetStr);
7658  }
7659  if(AVI->Command == "jbo") //added at v2.15.0
7660  {
7661  TDateTime TTTime = TrainController->GetControllerTrainTime(28, AVI->EventTime, RptNum, IncrementalMinutes);
7662  if((DelayedRandMins >= 1) && !TimetableTime)
7663  {
7664  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7665  }
7666  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7667  {
7669  }
7670  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7671  {
7672  EventTime = Utilities->Format96HHMM(TTTime);
7673  }
7674  RetStr += "\nNew service joined by " + TrainController->GetRepeatHeadCode(68, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
7675  Utilities->CallLogPop(2595); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
7676  return(RetStr);
7677  }
7678  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh")) //added at v2.15.0
7679  {
7680  TDateTime TTTime = TrainController->GetControllerTrainTime(29, AVI->EventTime, RptNum, IncrementalMinutes);
7681  if((DelayedRandMins >= 1) && !TimetableTime)
7682  {
7683  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7684  }
7685  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7686  {
7688  }
7689  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7690  {
7691  EventTime = Utilities->Format96HHMM(TTTime);
7692  }
7693  RetStr += "\nNew service finishes and forms another new service at approx. " + EventTime;
7694  Utilities->CallLogPop(2615);
7695  return(RetStr);
7696  }
7697  if(AVI->Command == "Fjo") //added at v2.15.0
7698  {
7699  TDateTime TTTime = TrainController->GetControllerTrainTime(26, AVI->EventTime, RptNum, IncrementalMinutes);
7700  if((DelayedRandMins >= 1) && !TimetableTime)
7701  {
7702  EventTime = Utilities->Format96HHMM(TTTime + TDateTime(DelayedRandMins/1440));
7703  }
7704  else if((TrainController->TTClockTime > TTTime) && !TimetableTime)
7705  {
7707  }
7708  else //((DelayedRandMins == 0) && (TTClockTime <= TTTime)) || TimetableTime
7709  {
7710  EventTime = Utilities->Format96HHMM(TTTime);
7711  }
7712  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(69, AVI->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at approx. " + EventTime;
7713  Utilities->CallLogPop(2605); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
7714  return(RetStr);
7715  }
7716  if(AVI->Command == "Frh") //added at v2.15.0
7717  {
7718  RetStr += "\nNew service finishes and remains at the location.";
7719  Utilities->CallLogPop(2606);
7720  return(RetStr);
7721  }
7722  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
7723  {
7724  if(TimetableTime) //don't add random delay
7725  {
7726  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(17, AVI->DepartureTime, RptNum, IncrementalMinutes));
7727  if(CDTFlag)
7728  {
7729  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7730  {
7731  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
7732  }
7733  else
7734  {
7735  RetStr += "\nNew service changes direction then departs at " + DepTime;
7736  }
7737  }
7738  else
7739  {
7740  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7741  {
7742  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
7743  }
7744  else
7745  {
7746  RetStr += "\nNew service departs at " + DepTime;
7747  }
7748  }
7749  }
7750  else if(DelayedRandMins >= 1)//add random delay
7751  {
7752  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(24, AVI->DepartureTime + TDateTime(DelayedRandMins/1440), RptNum, IncrementalMinutes));
7753  if(CDTFlag)
7754  {
7755  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7756  {
7757  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
7758  }
7759  else
7760  {
7761  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
7762  }
7763  }
7764  else
7765  {
7766  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7767  {
7768  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
7769  }
7770  else
7771  {
7772  RetStr += "\nNew service departs at approx. " + DepTime;
7773  }
7774  }
7775  }
7776  else //no random delay but may be delayed for other reasons
7777  {
7778  TDateTime TTTime = TrainController->GetControllerTrainTime(25, AVI->DepartureTime, RptNum, IncrementalMinutes);
7779  if(TrainController->TTClockTime > TTTime)
7780  {
7782  }
7783  else
7784  {
7785  DepTime = Utilities->Format96HHMM(TTTime);
7786  }
7787  if(CDTFlag)
7788  {
7789  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7790  {
7791  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at approx. " + DepTime;
7792  }
7793  else
7794  {
7795  RetStr += "\nNew service changes direction then departs at approx. " + DepTime;
7796  }
7797  }
7798  else
7799  {
7800  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
7801  {
7802  RetStr += "\nNew service departs towards " + TowardsLocation + " at approx. " + DepTime;
7803  }
7804  else
7805  {
7806  RetStr += "\nNew service departs at approx. " + DepTime;
7807  }
7808  }
7809  }
7810  Utilities->CallLogPop(2236);
7811  return(RetStr);
7812  }
7813  }
7814  Utilities->CallLogPop(2208);
7815  return(RetStr); //if reach here then RetStr doesn't change
7816 }
7817 
7818 // ---------------------------------------------------------------------------
7819 
7821 // Enter with Ptr pointing to first action to be listed (i.e. next action)
7822 // If there are actions to be skipped but a departure is awaited (SkippedDeparture = true) then after the departure Ptr moves forward by SkipPtrValue
7823 {
7824  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - &TrainDataEntryPtr->ActionVector.front()) +
7825  ",FloatingTimetableString" + "," + HeadCode);
7826  AnsiString RetStr = "", PartStr = "";
7827  int Count = 0;
7828  bool SkipDep = false, SkipDepActedOn = false; //SkipDepActedOn ensures only one SkipDep acted on
7829  AnsiString LocName = Ptr->LocationName;
7830 
7831  if((Ptr->Command != "") && (Ptr->Command[1] == 'S') && (TrainMode == Timetable))
7832  // can start in signaller control so exclude this
7833  {
7834  throw Exception("Error - start entry in FloatingTimetableString");
7835  }
7836  TActionVectorEntry *EntryPtr = Ptr; //used in TimeTimeLoc check later
7837  bool FirstPass = true;
7838  Ptr--; // because incremented at start of loop
7839 
7840  // different first TimeTimeLoc display if in signaller control
7841  do
7842  {
7843  Ptr++;
7844  if((Ptr->FormatType == Repeat) || TimetableFinished)
7845  {
7846  break;
7847  }
7848  if((Ptr->FormatType == TimeTimeLoc) && FirstPass)
7849  {
7850  AnsiString TrainLoc = "";
7851  if(TrainMode == Timetable)
7852  {
7853  if(TrainAtLocation(1, TrainLoc) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
7854  {
7855  PartStr = Utilities->Format96HHMM(GetTrainTime(33, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7856  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7857  {
7858  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7859  }
7860  }
7861  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
7862  {
7863  PartStr = Utilities->Format96HHMM(GetTrainTime(34, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
7864  }
7865  else
7866  {
7867  PartStr = Utilities->Format96HHMM(GetTrainTime(16, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
7868  Utilities->Format96HHMM(GetTrainTime(17, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7869  Count++; // because there are 2 entries
7870  }
7871  }
7872  else // TrainMode == Signaller
7873  {
7874  if(DepartureTimeSet)
7875  {
7876  PartStr = Utilities->Format96HHMM(GetTrainTime(37, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7877  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7878  {
7879  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7880  }
7881  }
7882  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
7883  {
7884  PartStr = Utilities->Format96HHMM(GetTrainTime(38, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
7885  }
7886  else
7887  {
7888  PartStr = Utilities->Format96HHMM(GetTrainTime(39, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
7889  Utilities->Format96HHMM(GetTrainTime(40, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7890  Count++; // because there are 2 entries
7891  }
7892  }
7893  }
7894  else if((Ptr->FormatType == TimeTimeLoc) && !FirstPass)
7895  {
7896  AnsiString TrainLoc = "";
7897  if((TrainAtLocation(2, TrainLoc)) && (TrainLoc == Ptr->LocationName) && (Ptr == EntryPtr)) //added '&& (Ptr == EntryPtr)' at v2.6.0 when allow multiple same location entries
7898  {
7899  PartStr = Utilities->Format96HHMM(GetTrainTime(41, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7900  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7901  {
7902  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7903  }
7904  }
7905  else if(Ptr->ArrivalTime == Ptr->DepartureTime)
7906  {
7907  PartStr = Utilities->Format96HHMM(GetTrainTime(42, Ptr->ArrivalTime)) + ": Arrive & depart from " + Ptr->LocationName;
7908  }
7909  else
7910  {
7911  PartStr = Utilities->Format96HHMM(GetTrainTime(43, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName + '\n' +
7912  Utilities->Format96HHMM(GetTrainTime(44, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7913  Count++; // because there are 2 entries
7914  }
7915  }
7916  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
7917  {
7918  PartStr = Utilities->Format96HHMM(GetTrainTime(18, Ptr->ArrivalTime)) + ": Arrive at " + Ptr->LocationName;
7919  }
7920  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
7921  {
7922  PartStr = Utilities->Format96HHMM(GetTrainTime(19, Ptr->DepartureTime)) + ": Depart from " + Ptr->LocationName;
7923  if((LocName == Ptr->LocationName) && (LocName != "") && SkippedDeparture && !SkipDepActedOn)
7924  {
7925  SkipDep = true; //0 for incremental minutes because don't reduce the departure time when later actions have been skipped
7926  }
7927  }
7928  else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture) //added at v2.12.0 for becoming new service early (see BecomeNewService)
7929  { //note that TreatPassAsTimeLocDeparture can't be set if have SkippedDeparture
7930  PartStr = Utilities->Format96HHMM(GetTrainTime(47, Ptr->EventTime)) + ": Depart from " + Ptr->LocationName;
7931  }
7932  else if(Ptr->FormatType == PassTime) //must come after 'else if((Ptr->FormatType == PassTime) && TreatPassAsTimeLocDeparture)'
7933  {
7934  PartStr = Utilities->Format96HHMM(GetTrainTime(30, Ptr->EventTime)) + ": Pass " + Ptr->LocationName;
7935  }
7936  else if(Ptr->Command == "Fns")
7937  {
7938  PartStr = Utilities->Format96HHMM(GetTrainTime(20, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(15,
7939  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7940  PartStr = GetNewServiceDepartureInfo(5, Ptr, RepeatNumber, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to PartStr
7941  }
7942  else if(Ptr->Command == "F-nshs")
7943  {
7944  PartStr = Utilities->Format96HHMM(GetTrainTime(35, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode + " at " +
7945  Ptr->LocationName;
7946  PartStr = GetNewServiceDepartureInfo(6, Ptr, 0, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7947  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
7948  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
7949  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
7950  }
7951  else if((Ptr->Command == "Fns-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
7952  {
7953  PartStr = Utilities->Format96HHMM(GetTrainTime(21, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(16,
7954  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
7955  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
7956  PartStr = GetNewServiceDepartureInfo(7, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7957  }
7958  else if((Ptr->Command == "Fns-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7959  {
7960  PartStr = Utilities->Format96HHMM(GetTrainTime(22, Ptr->EventTime)) + ": Form new service " + Ptr->NonRepeatingShuttleLinkHeadCode,
7961  +" at " + Ptr->LocationName;
7962  PartStr = GetNewServiceDepartureInfo(8, Ptr, 0, Ptr->NonRepeatingShuttleLinkEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7963  }
7964  else if((Ptr->Command == "Frh-sh") && (RepeatNumber < (TrainDataEntryPtr->NumberOfTrains - 1))) // not the last repeat number
7965  {
7966  PartStr = Utilities->Format96HHMM(GetTrainTime(23, Ptr->EventTime)) + ": Form new service " + TrainController->GetRepeatHeadCode(17,
7967  Ptr->OtherHeadCode, RepeatNumber + 1, IncrementalDigits) + " at " + Ptr->LocationName;
7968  // use RepeatNumber+1 because it's the repeat number of the NEXT shuttle service that is relevant
7969  PartStr = GetNewServiceDepartureInfo(9, Ptr, RepeatNumber + 1, Ptr->LinkedTrainEntryPtr, PartStr, true); //if there is a next service this adds the new service departure time to RetStr
7970  }
7971  else if((Ptr->Command == "Frh-sh") && (RepeatNumber >= (TrainDataEntryPtr->NumberOfTrains - 1))) // last repeat number
7972  {
7973  PartStr = "Terminate at " + Ptr->LocationName;
7974  }
7975  else if(Ptr->Command == "Frh")
7976  {
7977  PartStr = "Terminate at " + Ptr->LocationName;
7978  }
7979  else if(Ptr->Command == "Fer")
7980  {
7981  AnsiString AllowedExits = "";
7982  PartStr = Utilities->Format96HHMM(GetTrainTime(24, Ptr->EventTime)) + ": Exit railway" + TrainController->GetExitLocationAndAt(2, Ptr->ExitList, AllowedExits) + AllowedExits;
7983  }
7984  else if(Ptr->Command == "Fjo")
7985  {
7986  PartStr = Utilities->Format96HHMM(GetTrainTime(25, Ptr->EventTime)) + ": Join " + TrainController->GetRepeatHeadCode(18, Ptr->OtherHeadCode,
7987  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7988  }
7989  else if(Ptr->Command == "jbo")
7990  {
7991  PartStr = Utilities->Format96HHMM(GetTrainTime(26, Ptr->EventTime)) + ": Joined by " + TrainController->GetRepeatHeadCode(19, Ptr->OtherHeadCode,
7992  RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7993  }
7994  else if(Ptr->Command == "fsp")
7995  {
7996  PartStr = Utilities->Format96HHMM(GetTrainTime(27, Ptr->EventTime)) + ": Front split to " + TrainController->GetRepeatHeadCode(20,
7997  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
7998  if(Ptr->SplitDistribution != "")
7999  {
8000  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8001  }
8002  else
8003  {
8004  PartStr+= ", split mass%-Power% = 50-50";
8005  }
8006  }
8007  else if(Ptr->Command == "rsp")
8008  {
8009  PartStr = Utilities->Format96HHMM(GetTrainTime(28, Ptr->EventTime)) + ": Rear split to " + TrainController->GetRepeatHeadCode(21,
8010  Ptr->OtherHeadCode, RepeatNumber, IncrementalDigits) + " at " + Ptr->LocationName;
8011  if(Ptr->SplitDistribution != "")
8012  {
8013  PartStr+= ", split mass%-Power% = " + Ptr->SplitDistribution;
8014  }
8015  else
8016  {
8017  PartStr+= ", split mass%-Power% = 50-50";
8018  }
8019  }
8020  else if(Ptr->Command == "cdt")
8021  {
8022  PartStr = Utilities->Format96HHMM(GetTrainTime(29, Ptr->EventTime)) + ": Change direction at " + Ptr->LocationName;
8023  }
8024  else if(Ptr->Command == "dsc")
8025  {
8026  PartStr = Utilities->Format96HHMM(GetTrainTime(67, Ptr->EventTime)) + ": Change description at " + Ptr->LocationName;
8027  }
8028  if(RetStr != "")
8029  {
8030  RetStr = RetStr + '\n' + PartStr;
8031  }
8032  else
8033  {
8034  RetStr = PartStr;
8035  }
8036  FirstPass = false;
8037  Count++;
8038 
8039  if(SkipDep)
8040  {
8041  Ptr = &(TrainDataEntryPtr->ActionVector.at(0)) + SkipPtrValue;
8042  Ptr--; //it is incremented at the start of the next loop
8043  SkipDep = false;
8044  SkipDepActedOn = true;
8045  }
8046  }
8047  while(!TimetableFinished && (Count < 32) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
8048  // limit of 32 allows a max of 34 entries (33 + 1 for the new service departure time) (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and
8049  // train status gives a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
8050  // forward as anyone should wish to see without looking at the full timetable
8051  if(TimetableFinished)
8052  {
8053  if(TrainMode == Timetable)
8054  {
8055  RetStr = "Timetable finished";
8056  }
8057  else
8058  {
8059  RetStr = "No timetable";
8060  }
8061  }
8062  Utilities->CallLogPop(1125);
8063  return("Timetable:\n" + RetStr);
8064 }
8065 
8066 // ---------------------------------------------------------------------------
8067 
8068 void TTrain::SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
8069 {
8070  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveOneSessionTrain" + "," + HeadCode);
8071  Utilities->SaveFileString(OutFile, HeadCode);
8074  Utilities->SaveFileInt(OutFile, StartSpeed);
8077  Utilities->SaveFileInt(OutFile, RepeatNumber);
8080  Utilities->SaveFileInt(OutFile, Mass);
8083  Utilities->SaveFileDouble(OutFile, EntrySpeed);
8090  Utilities->SaveFileDouble(OutFile, BrakeRate);
8094  Utilities->SaveFileDouble(OutFile, double(EntryTime));
8095  Utilities->SaveFileDouble(OutFile, double(ExitTimeHalf));
8096  Utilities->SaveFileDouble(OutFile, double(ExitTimeFull));
8097  Utilities->SaveFileDouble(OutFile, double(ReleaseTime));
8098  Utilities->SaveFileDouble(OutFile, double(TRSTime));
8099  Utilities->SaveFileDouble(OutFile, double(LastActionTime));
8103  Utilities->SaveFileInt(OutFile, (short)TrainMode);
8108  Utilities->SaveFileBool(OutFile, Derailed);
8110  Utilities->SaveFileBool(OutFile, Crashed);
8117  Utilities->SaveFileBool(OutFile, NotInService);
8118  Utilities->SaveFileBool(OutFile, Plotted);
8119  Utilities->SaveFileBool(OutFile, TrainGone);
8120  Utilities->SaveFileBool(OutFile, SPADFlag);
8122  Utilities->SaveFileInt(OutFile, HOffset[0]);
8123  Utilities->SaveFileInt(OutFile, HOffset[1]);
8124  Utilities->SaveFileInt(OutFile, HOffset[2]);
8125  Utilities->SaveFileInt(OutFile, HOffset[3]);
8126  Utilities->SaveFileInt(OutFile, VOffset[0]);
8127  Utilities->SaveFileInt(OutFile, VOffset[1]);
8128  Utilities->SaveFileInt(OutFile, VOffset[2]);
8129  Utilities->SaveFileInt(OutFile, VOffset[3]);
8130  Utilities->SaveFileInt(OutFile, PlotElement[0]);
8131  Utilities->SaveFileInt(OutFile, PlotElement[1]);
8132  Utilities->SaveFileInt(OutFile, PlotElement[2]);
8133  Utilities->SaveFileInt(OutFile, PlotElement[3]);
8134  Utilities->SaveFileInt(OutFile, PlotEntryPos[0]);
8135  Utilities->SaveFileInt(OutFile, PlotEntryPos[1]);
8136  Utilities->SaveFileInt(OutFile, PlotEntryPos[2]);
8137  Utilities->SaveFileInt(OutFile, PlotEntryPos[3]);
8139  Utilities->SaveFileInt(OutFile, (short)Straddle);
8140  Utilities->SaveFileInt(OutFile, NextTrainID);
8141  Utilities->SaveFileInt(OutFile, TrainID);
8142  Utilities->SaveFileInt(OutFile, LeadElement);
8143  Utilities->SaveFileInt(OutFile, LeadEntryPos);
8144  Utilities->SaveFileInt(OutFile, LeadExitPos);
8145  Utilities->SaveFileInt(OutFile, MidElement);
8146  Utilities->SaveFileInt(OutFile, MidEntryPos);
8147  Utilities->SaveFileInt(OutFile, MidExitPos);
8148  Utilities->SaveFileInt(OutFile, LagElement);
8149  Utilities->SaveFileInt(OutFile, LagEntryPos);
8150  Utilities->SaveFileInt(OutFile, LagExitPos);
8151  int ColourNumber;
8152 
8154  {
8155  ColourNumber = 0;
8156  }
8158  {
8159  ColourNumber = 1;
8160  }
8162  {
8163  ColourNumber = 2;
8164  }
8166  {
8167  ColourNumber = 3;
8168  }
8170  {
8171  ColourNumber = 4;
8172  }
8174  {
8175  ColourNumber = 5;
8176  }
8178  {
8179  ColourNumber = 6;
8180  }
8182  {
8183  ColourNumber = 7;
8184  }
8186  {
8187  ColourNumber = 8;
8188  }
8190  {
8191  ColourNumber = 9;
8192  }
8194  {
8195  ColourNumber = 10;
8196  }
8198  {
8199  ColourNumber = 11;
8200  }
8202  {
8203  ColourNumber = 12;
8204  }
8205  else if(BackgroundColour == clTRSBackground)
8206  {
8207  ColourNumber = 13;
8208  }
8210  {
8211  ColourNumber = 14; // added at v2.4.0
8212  }
8213  Utilities->SaveFileInt(OutFile, ColourNumber);
8214 
8215  // additional data
8216  bool ForwardHeadCode;
8217 
8218  if(HeadCodePosition[3] == HeadCodeGrPtr[3])
8219  {
8220  ForwardHeadCode = true;
8221  }
8222  // can't use 'if(HeadCodePosition[0] == HeadCodeGrPtr[0])' as HeadCodePosition[0] is set to FrontCodePtr
8223  else
8224  {
8225  ForwardHeadCode = false;
8226  }
8227  Utilities->SaveFileBool(OutFile, ForwardHeadCode);
8228 
8229  int TrainDataEntryValue = TrainDataEntryPtr - &(TrainController->TrainDataVector.at(0));
8230 
8231  Utilities->SaveFileInt(OutFile, TrainDataEntryValue);
8232  int ActionVectorEntryValue = ActionVectorEntryPtr - &(TrainDataEntryPtr->ActionVector.at(0));
8233 
8234  Utilities->SaveFileInt(OutFile, ActionVectorEntryValue);
8235  // now the marker comes next which was ****** originally but used for RestoreTimetableLocation as well some time ago (came before the asterisks)
8236  // but at v2.4.0 need to include StoppedWithoutPower, while keeping length of marker at 6, because that is tested in earlier versions
8237  // so use the last asterisk position for this - 0 for false & 1 for true
8238  // note that failed train data is handled in InterfaceUnit.cpp & stored after the performance file
8239  AnsiString Marker;
8240 
8242  {
8243  Marker = "*****1";
8244  }
8245  else
8246  {
8247  Marker = "*****0";
8248  }
8249  if(RestoreTimetableLocation == "")
8250  {
8251  Utilities->SaveFileString(OutFile, Marker);
8252  }
8253  else
8254  {
8255  AnsiString CombinedString = RestoreTimetableLocation + Marker;
8256  Utilities->SaveFileString(OutFile, CombinedString);
8257  // RestoreTimetableLocation + marker
8258  }
8259  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - it should have been saved earlier
8260  Utilities->CallLogPop(1457);
8261 }
8262 
8263 // ---------------------------------------------------------------------------
8264 
8265 void TTrain::LoadOneSessionTrain(int Caller, std::ifstream &InFile)
8266 {
8267  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadOneSessionTrain"); // don't have headcode yet
8268  HeadCode = Utilities->LoadFileString(InFile);
8271  StartSpeed = Utilities->LoadFileInt(InFile);
8273  if(SignallerMaxSpeed < 10)
8274  {
8275  SignallerMaxSpeed = 10; // added at v0.6 to avoid low max speeds
8276  }
8278  RepeatNumber = Utilities->LoadFileInt(InFile);
8281  Mass = Utilities->LoadFileInt(InFile);
8284  {
8286  }
8287  // above added at v2.1.0 for legacy session files where value may not have been limited
8289  EntrySpeed = Utilities->LoadFileDouble(InFile);
8293  if(TimetableMaxRunningSpeed < 10)
8294  {
8295  TimetableMaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8296  }
8298  if(MaxRunningSpeed < 10)
8299  {
8300  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
8301  }
8304  BrakeRate = Utilities->LoadFileDouble(InFile);
8308  EntryTime = TDateTime(Utilities->LoadFileDouble(InFile));
8309  ExitTimeHalf = TDateTime(Utilities->LoadFileDouble(InFile));
8310  ExitTimeFull = TDateTime(Utilities->LoadFileDouble(InFile));
8311  ReleaseTime = TDateTime(Utilities->LoadFileDouble(InFile));
8312  TRSTime = TDateTime(Utilities->LoadFileDouble(InFile));
8313  LastActionTime = TDateTime(Utilities->LoadFileDouble(InFile));
8322  Derailed = Utilities->LoadFileBool(InFile);
8324  Crashed = Utilities->LoadFileBool(InFile);
8331  NotInService = Utilities->LoadFileBool(InFile);
8332  Plotted = Utilities->LoadFileBool(InFile);
8333  TrainGone = Utilities->LoadFileBool(InFile);
8334  SPADFlag = Utilities->LoadFileBool(InFile);
8336  HOffset[0] = Utilities->LoadFileInt(InFile);
8337  HOffset[1] = Utilities->LoadFileInt(InFile);
8338  HOffset[2] = Utilities->LoadFileInt(InFile);
8339  HOffset[3] = Utilities->LoadFileInt(InFile);
8340  VOffset[0] = Utilities->LoadFileInt(InFile);
8341  VOffset[1] = Utilities->LoadFileInt(InFile);
8342  VOffset[2] = Utilities->LoadFileInt(InFile);
8343  VOffset[3] = Utilities->LoadFileInt(InFile);
8344  PlotElement[0] = Utilities->LoadFileInt(InFile);
8345  PlotElement[1] = Utilities->LoadFileInt(InFile);
8346  PlotElement[2] = Utilities->LoadFileInt(InFile);
8347  PlotElement[3] = Utilities->LoadFileInt(InFile);
8348  PlotEntryPos[0] = Utilities->LoadFileInt(InFile);
8349  PlotEntryPos[1] = Utilities->LoadFileInt(InFile);
8350  PlotEntryPos[2] = Utilities->LoadFileInt(InFile);
8351  PlotEntryPos[3] = Utilities->LoadFileInt(InFile);
8353  Straddle = (TStraddle)(Utilities->LoadFileInt(InFile));
8354  NextTrainID = Utilities->LoadFileInt(InFile);
8355  // will be same for all but best to save all anyway
8356  TrainID = Utilities->LoadFileInt(InFile);
8357  LeadElement = Utilities->LoadFileInt(InFile);
8358  LeadEntryPos = Utilities->LoadFileInt(InFile);
8359  LeadExitPos = Utilities->LoadFileInt(InFile);
8360  MidElement = Utilities->LoadFileInt(InFile);
8361  MidEntryPos = Utilities->LoadFileInt(InFile);
8362  MidExitPos = Utilities->LoadFileInt(InFile);
8363  LagElement = Utilities->LoadFileInt(InFile);
8364  LagEntryPos = Utilities->LoadFileInt(InFile);
8365  LagExitPos = Utilities->LoadFileInt(InFile);
8366  int ColourNumber = TColor(Utilities->LoadFileInt(InFile));
8367 
8368  if(ColourNumber == 0)
8369  {
8371  }
8372  else if(ColourNumber == 1)
8373  {
8375  }
8376  else if(ColourNumber == 2)
8377  {
8379  }
8380  else if(ColourNumber == 3)
8381  {
8383  }
8384  else if(ColourNumber == 4)
8385  {
8387  }
8388  else if(ColourNumber == 5)
8389  {
8391  }
8392  else if(ColourNumber == 6)
8393  {
8395  }
8396  else if(ColourNumber == 7)
8397  {
8399  }
8400  else if(ColourNumber == 8)
8401  {
8403  }
8404  else if(ColourNumber == 9)
8405  {
8407  }
8408  else if(ColourNumber == 10)
8409  {
8411  }
8412  else if(ColourNumber == 11)
8413  {
8415  }
8416  else if(ColourNumber == 12)
8417  {
8419  }
8420  else if(ColourNumber == 13)
8421  {
8423  }
8424  else if(ColourNumber == 14)
8425  {
8426  BackgroundColour = clTrainFailedBackground; // added at v2.4.0
8427 
8428  }
8429  // additional data
8431  // sets the BackgroundColour to the loaded value
8432  bool ForwardHeadCode = Utilities->LoadFileBool(InFile);
8433 
8434  if(ForwardHeadCode)
8435  {
8436  for(int x = 0; x < 4; x++)
8437  {
8439  }
8440  }
8441  else
8442  {
8443  for(int x = 0; x < 4; x++)
8444  {
8445  HeadCodePosition[x] = HeadCodeGrPtr[3 - x];
8446  }
8447  }
8448  // if crashed & in timetable mode then change FrontCodePtr to black, if in signaller mode then change to blue whether crashed or not
8449  if(TrainMode == Timetable)
8450  {
8451  if(Crashed)
8452  {
8454  }
8455  else
8456  {
8458  }
8459  }
8460  else
8461  {
8463  }
8465  // pick up background bitmaps, none if MidLag as no train plotted - entering at continuation
8466  if(Straddle == LeadMid)
8467  {
8468  if(LeadElement > -1)
8469  {
8471  }
8472  if(LeadElement > -1)
8473  {
8475  }
8476  if(MidElement > -1)
8477  {
8479  }
8480  if(MidElement > -1)
8481  {
8483  }
8484  }
8485  else if(Straddle == LeadMidLag)
8486  {
8487  if(LeadElement > -1)
8488  {
8490  }
8491  if(MidElement > -1)
8492  {
8494  }
8495  if(MidElement > -1)
8496  {
8498  }
8499  if(LagElement > -1)
8500  {
8502  }
8503  }
8504  int TrainDataEntryValue = Utilities->LoadFileInt(InFile);
8505 
8506  TrainDataEntryPtr = &(TrainController->TrainDataVector.at(0)) + TrainDataEntryValue;
8507  int ActionVectorEntryValue = Utilities->LoadFileInt(InFile);
8508 
8509  ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0)) + ActionVectorEntryValue;
8510 
8511  // need to set the TrainID if arriving at a continuation but hasn't been plotted yet
8512  if(LeadElement > -1)
8513  // need to include this in case train exiting & no lead element
8514  {
8516  {
8517  Track->TrackElementAt(668, LeadElement).TrainIDOnElement = TrainID; // no need to stop gap flashing if a continuation
8518  }
8519  }
8520  AValue = sqrt(2 * PowerAtRail / Mass);
8521 
8522  AnsiString LocationAndMarker = Utilities->LoadFileString(InFile);
8523 
8524  // possible RestoreTimetableLocation + Marker, where Marker is
8525  // "*****0" for !StoppedWithoutPower and "*****1" otherwise (from v2.4.0)
8526  // Note: including RestoreTimetableLocation with the marker is to correct an oversight - RestoreTimetableLocation should have been saved earlier
8527  // added at beta v0.2e
8528  if((LocationAndMarker[1] != '*') && (LocationAndMarker.Length() > 6))
8529  // name not allowed to include the '*' character
8530  {
8531  AnsiString Location = LocationAndMarker.SubString(1, LocationAndMarker.Length() - 6);
8532  bool GiveMessagesFalse = false;
8533  bool CheckLocationsExistInRailwayTrue = true;
8534  if(TrainController->CheckLocationValidity(3, Location, GiveMessagesFalse, CheckLocationsExistInRailwayTrue))
8535  {
8536  // otherwise take no action
8537  RestoreTimetableLocation = Location;
8538  }
8539  }
8540  AnsiString Marker = LocationAndMarker.SubString(LocationAndMarker.Length() - 5, 6);
8541 
8542  StoppedWithoutPower = false;
8543  if(Marker[6] == '1')
8544  {
8545  StoppedWithoutPower = true;
8546  }
8547  Utilities->CallLogPop(1458);
8548 }
8549 
8550 // ---------------------------------------------------------------------------
8551 
8552 bool TTrain::CheckOneSessionTrain(std::ifstream &InFile)
8553 {
8555  {
8556  return(false); // HeadCode
8557 
8558  }
8559  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8560  {
8561  return(false); // RearStartElement
8562 
8563  }
8564  if(!Utilities->CheckFileInt(InFile, 0, 3))
8565  {
8566  return(false); // RearStartExitPos
8567 
8568  }
8569  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
8570  {
8571  return(false); // StartSpeed
8572 
8573  }
8574  if(!Utilities->CheckFileInt(InFile, 0, MaximumSpeedLimit))
8575  {
8576  return(false); // SignallerMaxSpeed
8577 
8578  }
8579  if(!Utilities->CheckFileBool(InFile))
8580  {
8581  return(false); // HoldAtLocationInTTMode
8582 
8583  }
8584  if(!Utilities->CheckFileInt(InFile, 0, 5760))
8585  {
8586  return(false); // RepeatNumber (max 96 x 60 at 1 min intervals)
8587 
8588  }
8589  if(!Utilities->CheckFileInt(InFile, 0, 5760))
8590  {
8591  return(false); // IncrementalMinutes (max 96 x 60)
8592 
8593  }
8594  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8595  {
8596  return(false); // IncrementalDigits
8597 
8598  }
8599  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
8600  {
8601  return(false); // Mass
8602 
8603  }
8604  if(!Utilities->CheckFileInt(InFile, 0, 100000000))
8605  {
8606  return(false);
8607  }
8608  // FrontElementSpeedLimit - changed at v2.1.0 - effectively
8609  // not checked so as to allow for legacy session files, for new session files limit is set when loaded, see above
8610  if(!Utilities->CheckFileInt(InFile, 0, 10000000))
8611  {
8612  return(false); // FrontElementLength
8613 
8614  }
8615  if(!Utilities->CheckFileDouble(InFile))
8616  {
8617  return(false); // EntrySpeed
8618 
8619  }
8620  if(!Utilities->CheckFileDouble(InFile))
8621  {
8622  return(false); // ExitSpeedHalf
8623 
8624  }
8625  if(!Utilities->CheckFileDouble(InFile))
8626  {
8627  return(false); // ExitSpeedFull
8628 
8629  }
8630  if(!Utilities->CheckFileDouble(InFile))
8631  {
8632  return(false); // TimetableMaxRunningSpeed
8633 
8634  }
8635  if(!Utilities->CheckFileDouble(InFile))
8636  {
8637  return(false); // MaxRunningSpeed
8638 
8639  }
8640  if(!Utilities->CheckFileDouble(InFile))
8641  {
8642  return(false); // MaxExitSpeed
8643 
8644  }
8645  if(!Utilities->CheckFileDouble(InFile))
8646  {
8647  return(false); // MaxBrakeRate
8648 
8649  }
8650  if(!Utilities->CheckFileDouble(InFile))
8651  {
8652  return(false); // BrakeRate
8653 
8654  }
8655  if(!Utilities->CheckFileDouble(InFile))
8656  {
8657  return(false); // PowerAtRail
8658 
8659  }
8660  if(!Utilities->CheckFileBool(InFile))
8661  {
8662  return(false); // FirstHalfMove
8663 
8664  }
8665  if(!Utilities->CheckFileBool(InFile))
8666  {
8667  return(false); // OneLengthAccelDecel
8668 
8669  }
8670  if(!Utilities->CheckFileDouble(InFile))
8671  {
8672  return(false); // double(EntryTime)
8673 
8674  }
8675  if(!Utilities->CheckFileDouble(InFile))
8676  {
8677  return(false); // double(ExitTimeHalf)
8678 
8679  }
8680  if(!Utilities->CheckFileDouble(InFile))
8681  {
8682  return(false); // double(ExitTimeFull)
8683 
8684  }
8685  if(!Utilities->CheckFileDouble(InFile))
8686  {
8687  return(false); // double(ReleaseTime)
8688 
8689  }
8690  if(!Utilities->CheckFileDouble(InFile))
8691  {
8692  return(false); // double(TRSTime)
8693 
8694  }
8695  if(!Utilities->CheckFileDouble(InFile))
8696  {
8697  return(false); // double(LastActionTime)
8698 
8699  }
8700  if(!Utilities->CheckFileBool(InFile))
8701  {
8702  return(false); // CallingOnFlag
8703 
8704  }
8705  if(!Utilities->CheckFileBool(InFile))
8706  {
8707  return(false); // BeingCalledOn
8708 
8709  }
8710  if(!Utilities->CheckFileBool(InFile))
8711  {
8712  return(false); // DepartureTimeSet
8713 
8714  }
8715  if(!Utilities->CheckFileInt(InFile, 0, 2))
8716  {
8717  return(false); // (short)TrainMode
8718 
8719  }
8720  if(!Utilities->CheckFileBool(InFile))
8721  {
8722  return(false); // TimetableFinished
8723 
8724  }
8725  if(!Utilities->CheckFileBool(InFile))
8726  {
8727  return(false); // LastActionDelayFlag
8728 
8729  }
8730  if(!Utilities->CheckFileBool(InFile))
8731  {
8732  return(false); // SignallerRemoved
8733 
8734  }
8735  if(!Utilities->CheckFileBool(InFile))
8736  {
8737  return(false); // TerminatedMessageSent
8738 
8739  }
8740  if(!Utilities->CheckFileBool(InFile))
8741  {
8742  return(false); // Derailed
8743 
8744  }
8745  if(!Utilities->CheckFileBool(InFile))
8746  {
8747  return(false); // DerailPending
8748 
8749  }
8750  if(!Utilities->CheckFileBool(InFile))
8751  {
8752  return(false); // Crashed
8753 
8754  }
8755  if(!Utilities->CheckFileBool(InFile))
8756  {
8757  return(false); // StoppedAtBuffers
8758 
8759  }
8760  if(!Utilities->CheckFileBool(InFile))
8761  {
8762  return(false); // StoppedAtSignal
8763 
8764  }
8765  if(!Utilities->CheckFileBool(InFile))
8766  {
8767  return(false); // StoppedAtLocation
8768 
8769  }
8770  if(!Utilities->CheckFileBool(InFile))
8771  {
8772  return(false); // SignallerStopped
8773 
8774  }
8775  if(!Utilities->CheckFileBool(InFile))
8776  {
8777  return(false); // StoppedAfterSPAD
8778 
8779  }
8780  if(!Utilities->CheckFileBool(InFile))
8781  {
8782  return(false); // StoppedForTrainInFront
8783 
8784  }
8785  if(!Utilities->CheckFileBool(InFile))
8786  {
8787  return(false); // NotInService
8788 
8789  }
8790  if(!Utilities->CheckFileBool(InFile))
8791  {
8792  return(false); // Plotted
8793 
8794  }
8795  if(!Utilities->CheckFileBool(InFile))
8796  {
8797  return(false); // TrainGone
8798 
8799  }
8800  if(!Utilities->CheckFileBool(InFile))
8801  {
8802  return(false); // SPADFlag
8803 
8804  }
8805  if(!Utilities->CheckFileBool(InFile))
8806  {
8807  return(false); // TimeTimeLocArrived
8808 
8809  }
8810  if(!Utilities->CheckFileInt(InFile, 0, 15))
8811  {
8812  return(false); // HOffset[0]
8813 
8814  }
8815  if(!Utilities->CheckFileInt(InFile, 0, 15))
8816  {
8817  return(false); // HOffset[1]
8818 
8819  }
8820  if(!Utilities->CheckFileInt(InFile, 0, 15))
8821  {
8822  return(false); // HOffset[2]
8823 
8824  }
8825  if(!Utilities->CheckFileInt(InFile, 0, 15))
8826  {
8827  return(false); // HOffset[3]
8828 
8829  }
8830  if(!Utilities->CheckFileInt(InFile, 0, 15))
8831  {
8832  return(false); // VOffset[0]
8833 
8834  }
8835  if(!Utilities->CheckFileInt(InFile, 0, 15))
8836  {
8837  return(false); // VOffset[1]
8838 
8839  }
8840  if(!Utilities->CheckFileInt(InFile, 0, 15))
8841  {
8842  return(false); // VOffset[2]
8843 
8844  }
8845  if(!Utilities->CheckFileInt(InFile, 0, 15))
8846  {
8847  return(false); // VOffset[3]
8848 
8849  }
8850  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8851  {
8852  return(false); // PlotElement[0]
8853 
8854  }
8855  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8856  {
8857  return(false); // PlotElement[1]
8858 
8859  }
8860  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8861  {
8862  return(false); // PlotElement[2]
8863 
8864  }
8865  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8866  {
8867  return(false); // PlotElement[3]
8868 
8869  }
8870  if(!Utilities->CheckFileInt(InFile, 0, 3))
8871  {
8872  return(false); // PlotEntryPos[0]
8873 
8874  }
8875  if(!Utilities->CheckFileInt(InFile, 0, 3))
8876  {
8877  return(false); // PlotEntryPos[1]
8878 
8879  }
8880  if(!Utilities->CheckFileInt(InFile, 0, 3))
8881  {
8882  return(false); // PlotEntryPos[2]
8883 
8884  }
8885  if(!Utilities->CheckFileInt(InFile, 0, 3))
8886  {
8887  return(false); // PlotEntryPos[3]
8888 
8889  }
8890  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8891  {
8892  return(false); // TrainCrashedInto
8893 
8894  }
8895  if(!Utilities->CheckFileInt(InFile, 0, 2))
8896  {
8897  return(false); // (short)Straddle
8898 
8899  }
8900  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8901  {
8902  return(false); // NextTrainID
8903 
8904  }
8905  if(!Utilities->CheckFileInt(InFile, 0, 1000000))
8906  {
8907  return(false); // TrainID
8908 
8909  }
8910  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8911  {
8912  return(false); // LeadElement
8913 
8914  }
8915  if(!Utilities->CheckFileInt(InFile, 0, 3))
8916  {
8917  return(false); // LeadEntryPos
8918 
8919  }
8920  if(!Utilities->CheckFileInt(InFile, 0, 3))
8921  {
8922  return(false); // LeadExitPos
8923 
8924  }
8925  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8926  {
8927  return(false); // MidElement
8928 
8929  }
8930  if(!Utilities->CheckFileInt(InFile, 0, 3))
8931  {
8932  return(false); // MidEntryPos
8933 
8934  }
8935  if(!Utilities->CheckFileInt(InFile, 0, 3))
8936  {
8937  return(false); // MidExitPos
8938 
8939  }
8940  if(!Utilities->CheckFileInt(InFile, -1, 1000000))
8941  {
8942  return(false); // LagElement
8943 
8944  }
8945  if(!Utilities->CheckFileInt(InFile, 0, 3))
8946  {
8947  return(false); // LagEntryPos
8948 
8949  }
8950  if(!Utilities->CheckFileInt(InFile, 0, 3))
8951  {
8952  return(false); // LagExitPos
8953 
8954  }
8955  if(!Utilities->CheckFileInt(InFile, 0, 14))
8956  {
8957  return(false);
8958  }
8959  // Background colour number //14 is failed colour at v2.4.0
8960  if(!Utilities->CheckFileBool(InFile))
8961  {
8962  return(false); // ForwardHeadCode
8963 
8964  }
8965  if(!Utilities->CheckFileInt(InFile, 0, 10000))
8966  {
8967  return(false); // TrainDataEntryValue
8968 
8969  }
8970  if(!Utilities->CheckFileInt(InFile, 0, 10000))
8971  {
8972  return(false); // ActionVectorEntryValue
8973 
8974  }
8976  {
8977  return(false); // End of train marker + possible RestoreTimetableLocation
8978 
8979  }
8980  // and StoppedWithoutPower flag
8981  return(true);
8982 }
8983 
8984 // ---------------------------------------------------------------------------
8985 
8986 void TTrain::PlotTrainInZoomOutMode(int Caller, bool Flash)
8987 {
8988  // order below reflects significance so earlier shows first, as may have more than one flag set
8989  // only plot flashing trains when Flash is true
8990 
8991 /*
8992  clCrashedBackground (TColor)0x0000FF red
8993  clDerailedBackground (TColor)0x0000FF red
8994  clSPADBackground (TColor)0x00FFFF yellow
8995  clTrainFailedBackground (TColor)0x0066FF orange new at v2.4.0
8996  clCallOnBackground (TColor)0xFF33FF light magenta
8997  clSignalStopBackground (TColor)0x00FF66 green
8998  clBufferAttentionNeeded (TColor)0xFFFF00 cyan
8999  clStationStopBackground (TColor)0xCCFFCC pale green
9000  clTRSBackground (TColor)0xFFCCFF light pink
9001  clBufferStopBackground (TColor)0xFFFFCC pale cyan
9002  clStoppedTrainInFront (TColor)0xFF9999 lavender blue
9003  clSignallerStopped (TColor)0x99CCFF caramel
9004  clNormalBackground (TColor)0xCCCCCC grey
9005 */
9006 
9007  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainInZoomOutMode" + "," + HeadCode);
9008  bool HideFlashingTrain = true;
9009  // hide it when Flash false so it blinks on and off
9010  // if don't hide it it stays displayed all the time
9011  Graphics::TBitmap *SmallTrainBitmap;
9012 
9013  // NB ensure retain same order as zoomed in order so colours correspond
9015  {
9016  TrainController->CrashWarning = true;
9017  SmallTrainBitmap = RailGraphics->smRed;
9018  }
9020  {
9022  SmallTrainBitmap = RailGraphics->smRed;
9023  }
9025  {
9026  TrainController->SPADWarning = true;
9027  SmallTrainBitmap = RailGraphics->smYellow;
9028  }
9030  {
9032  SmallTrainBitmap = RailGraphics->smOrange;
9033  }
9035  {
9037  SmallTrainBitmap = RailGraphics->smMagenta;
9038  }
9040  {
9042  SmallTrainBitmap = RailGraphics->smBrightGreen;
9043  }
9045  {
9047  SmallTrainBitmap = RailGraphics->smCyan;
9048  }
9050  {
9051  SmallTrainBitmap = RailGraphics->smPaleGreen;
9052  HideFlashingTrain = false;
9053  }
9055  {
9056  SmallTrainBitmap = RailGraphics->smCyan;
9057  HideFlashingTrain = false;
9058  }
9060  {
9061  SmallTrainBitmap = RailGraphics->smLightBlue;
9062  HideFlashingTrain = false;
9063  }
9065  {
9066  SmallTrainBitmap = RailGraphics->smCaramel;
9067  HideFlashingTrain = false;
9068  }
9069  else
9070  {
9071  SmallTrainBitmap = RailGraphics->smBlack; // moving
9072  HideFlashingTrain = false;
9073  }
9074  // now plot the new train
9075  // just plot lead & mid, unless lead == -1 in which case plot mid & lag
9076  if((LeadElement > -1) && (!HideFlashingTrain || Flash))
9077  {
9078  Display->PlotSmallOutput(4, Track->TrackElementAt(441, LeadElement).HLoc * 4, Track->TrackElementAt(442, LeadElement).VLoc * 4, SmallTrainBitmap);
9079  }
9080  if((MidElement > -1) && (!HideFlashingTrain || Flash))
9081  {
9082  Display->PlotSmallOutput(5, Track->TrackElementAt(443, MidElement).HLoc * 4, Track->TrackElementAt(444, MidElement).VLoc * 4, SmallTrainBitmap);
9083  }
9084  if((LeadElement == -1) && (LagElement > -1) && (!HideFlashingTrain || Flash))
9085  {
9086  Display->PlotSmallOutput(6, Track->TrackElementAt(445, LagElement).HLoc * 4, Track->TrackElementAt(446, LagElement).VLoc * 4, SmallTrainBitmap);
9087  }
9091  Utilities->CallLogPop(1459);
9092 }
9093 
9094 // ---------------------------------------------------------------------------
9095 
9097 {
9098  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrainInZoomOutMode," + AnsiString(TrainID) + "," + HeadCode);
9099  if(!Display->ZoomOutFlag)
9100  {
9101  Utilities->CallLogPop(1304);
9102  return;
9103  }
9104  for(int y = 0; y < 3; y++)
9105  {
9106  if(OldZoomOutElement[y] > -1)
9107  {
9108  bool FoundFlag = false;
9109  TTrackElement ATElement = Track->TrackElementAt(717, OldZoomOutElement[y]);
9110  TTrackElement IATElement1, IATElement2;
9111  // default elements to begin with
9112  Display->PlotSmallOutput(7, ATElement.HLoc * 4, ATElement.VLoc * 4, RailGraphics->smSolidBgnd); // plot the blank
9113  TTrack::TIMPair IMPair = Track->GetVectorPositionsFromInactiveTrackMap(14, ATElement.HLoc, ATElement.VLoc, FoundFlag);
9114  // Note, have to plot inactives before track because track has to overwrite NamedLocationElements
9115  if(FoundFlag)
9116  {
9117  IATElement1 = Track->InactiveTrackElementAt(87, IMPair.first);
9118  Display->PlotSmallOutput(8, IATElement1.HLoc * 4, IATElement1.VLoc * 4, IATElement1.SmallGraphicPtr);
9119  if(IMPair.first != IMPair.second)
9120  {
9121  IATElement2 = Track->InactiveTrackElementAt(88, IMPair.second);
9122  Display->PlotSmallOutput(9, IATElement2.HLoc * 4, IATElement2.VLoc * 4, IATElement2.SmallGraphicPtr);
9123  }
9124  }
9125  Display->PlotSmallOutput(10, ATElement.HLoc * 4, ATElement.VLoc * 4, ATElement.SmallGraphicPtr);
9126  }
9127  }
9128  Utilities->CallLogPop(1305);
9129 }
9130 
9131 // ---------------------------------------------------------------------------
9132 
9133 bool TTrain::TrainAtLocation(int Caller, AnsiString &LocationName)
9134 {
9135  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainAtLocation" + "," + HeadCode);
9136  LocationName = "";
9137  if(!RevisedStoppedAtLoc())
9138  {
9139  Utilities->CallLogPop(1398);
9140  return(false);
9141  }
9142  if(LeadElement > -1)
9143  {
9145  }
9146  if((LocationName == "") && (MidElement > -1))
9147  {
9148  LocationName = Track->TrackElementAt(682, MidElement).ActiveTrackElementName;
9149  }
9150  if((LocationName == "") && (LagElement > -1))
9151  {
9152  LocationName = Track->TrackElementAt(683, LagElement).ActiveTrackElementName;
9153  }
9154  if(LocationName == "")
9155  {
9156  throw Exception("Error - Location name not set in TrainAtLocation");
9157  }
9158  Utilities->CallLogPop(1399);
9159  return(true);
9160 }
9161 
9162 // ---------------------------------------------------------------------------
9163 
9164 void TTrain::PlotTrain(int Caller, TDisplay *Disp)
9165 {
9166  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrain" + "," + HeadCode);
9167  for(int x = 0; x < 4; x++)
9168  {
9169  PlotTrainGraphic(7, x, Disp);
9170  }
9171  Utilities->CallLogPop(647);
9172 }
9173 
9174 // ---------------------------------------------------------------------------
9175 
9176 void TTrain::WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
9177 {
9178  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainToImage" + "," + HeadCode);
9179  for(int x = 0; x < 4; x++)
9180  {
9181  if(PlotElement[x] > -1)
9182  {
9183  Bitmap->Canvas->Draw(((Track->TrackElementAt(744, PlotElement[x]).HLoc - Track->GetHLocMin()) * 16 + HOffset[x]),
9184  ((Track->TrackElementAt(745, PlotElement[x]).VLoc - Track->GetVLocMin()) * 16 + VOffset[x]), HeadCodePosition[x]);
9185  }
9186  }
9187  Utilities->CallLogPop(1708);
9188 }
9189 
9190 // ---------------------------------------------------------------------------
9191 
9192 bool TTrain::LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber) // added at v1.2.0
9193 {
9194  // return true for any part of train occupying LinkNumber at TrackVectorPosition, false for anything else, including no LinkNumber & no TrackVectorPosition
9195  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LinkOccupied," + AnsiString(TrackVectorPosition) + "," +
9196  AnsiString(LinkNumber) + "," + HeadCode);
9197 
9198 /* Note on Straddle: Straddle defines the actual train position wrt Lag, Mid & Lead elements at all times other than within UpdateTrain. Is only MidLag outside UpdateTrain
9199  on first entry at a continuation (with no train plotted), and that has no relevance here. In all other cases it is either LeadMid (when train fully
9200  on Lead & Mid elements) or LeadMidLag (when train straddling 3 elements).
9201 */
9202 
9203  // note that MidElement always fully occupied
9204  if((MidElement == TrackVectorPosition) && ((Track->TrackElementAt(883, TrackVectorPosition).Link[MidEntryPos] == LinkNumber) || (Track->TrackElementAt(884,
9205  TrackVectorPosition).Link[MidExitPos] == LinkNumber)))
9206  {
9207  Utilities->CallLogPop(2005);
9208  return(true);
9209  }
9210  if(Straddle == LeadMid)
9211  {
9212  if((LeadElement == TrackVectorPosition) && ((Track->TrackElementAt(885, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber) ||
9213  (Track->TrackElementAt(886, TrackVectorPosition).Link[LeadExitPos] == LinkNumber)))
9214  {
9215  Utilities->CallLogPop(2006);
9216  return(true);
9217  }
9218  }
9219  else if(Straddle == LeadMidLag)
9220  {
9221  if((LeadElement == TrackVectorPosition) && (Track->TrackElementAt(887, TrackVectorPosition).Link[LeadEntryPos] == LinkNumber))
9222  // only interested in LeadEntryPos as train not occupying ExitPos yet
9223  {
9224  Utilities->CallLogPop(2007);
9225  return(true);
9226  }
9227  else if((LagElement == TrackVectorPosition) && (Track->TrackElementAt(888, TrackVectorPosition).Link[LagExitPos] == LinkNumber))
9228  // only interested in LagExitPos as train has left EntryPos
9229  {
9230  Utilities->CallLogPop(2008);
9231  return(true);
9232  }
9233  }
9234  Utilities->CallLogPop(2009);
9235  return(false);
9236 }
9237 
9238 // ---------------------------------------------------------------------------
9239 
9240 float TTrain::CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair) // only called for running trains.
9245 // TimeToExit & ExitPair added for multiplayer
9246 {
9247  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcTimeToAct, " + HeadCode);
9248  int DistanceToRedSignal = 0, DistanceToExit = -1;
9249  float TimeToAct = 0, LastTimeToExit = TimeToExit;
9250  TimeToExit = -1;
9251  ExitPair.first = -1;
9252  ExitPair.second = -1;
9253  float MinsEarly = 0; //added at v2.6.1
9254  TDateTime DepartureTime; //added at v2.6.1 //ArrivalTime used instead of this at v2.9.0 but still calculate it in case need it later for some reason
9255  TDateTime ArrivalTime; //added at v2.9.0 as MinsEarly used DepartureTime which wasn't correct
9256  float TempTTE;
9257 
9258  if(TrainFailed && Stopped() && (TrainMode != Signaller))
9259  {
9260  Utilities->CallLogPop(2147);
9261  return(0); // time to act now, time to exit set above
9262  }
9263  if(SignallerStopped)
9264  {
9265  Utilities->CallLogPop(2080);
9266  return(-1); //time to exit set above
9267  }
9268  if(BeingCalledOn) //added at v2.7.0 so zero time to act cancelled right away
9269  {
9270  Utilities->CallLogPop(2266);
9271  return(-1); // time to exit set above
9272  }
9273 
9274  // check if exiting at a continuation, if so there's no action time needed but still need exit time & ExitPair
9275  if((LeadElement == -1) && (MidElement == -1) && (LagElement > -1) && (Track->TrackElementAt(1411, LagElement).TrackType == Continuation)
9276  /*&& (ExitSpeedFull > 1)*/) //LagElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9277  {
9278  if(Straddle == LeadMidLag) //only half of rear train element on exit, 0.5 lengths to exit
9279  {
9280  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9281  {
9282  TempTTE = (0.5 * Track->TrackElementAt(1412, LagElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9283  if(TempTTE < LastTimeToExit)
9284  {
9285  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9286  }
9287  else
9288  {
9289  TimeToExit = LastTimeToExit;
9290  }
9291  }
9292  else
9293  {
9294  TimeToExit = LastTimeToExit;
9295  }
9296  ExitPair.first = Track->TrackElementAt(1413, LagElement).HLoc;
9297  ExitPair.second = Track->TrackElementAt(1414, LagElement).VLoc;
9298  Utilities->CallLogPop(2342);
9299  return(-1);
9300  }
9301  else
9302  {
9303  TimeToExit = 0; //all train exited
9304  ExitPair.first = Track->TrackElementAt(1415, LagElement).HLoc;
9305  ExitPair.second = Track->TrackElementAt(1416, LagElement).VLoc;
9306  Utilities->CallLogPop(2343);
9307  return(-1);
9308  }
9309  }
9310  if((LeadElement == -1) && (MidElement > -1) && (Track->TrackElementAt(1417, MidElement).TrackType == Continuation)/* && (ExitSpeedFull > 1)*/)
9311  //here MidElement is the exit //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9312  {
9313  if(Straddle == LeadMidLag) //front element of train half off the exit, 1.5 lengths to exit
9314  {
9315  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9316  {
9317  TempTTE = (1.5 * Track->TrackElementAt(1418, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9318  if(TempTTE < LastTimeToExit)
9319  {
9320  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9321  }
9322  else
9323  {
9324  TimeToExit = LastTimeToExit;
9325  }
9326  }
9327  else
9328  {
9329  TimeToExit = LastTimeToExit;
9330  }
9331  ExitPair.first = Track->TrackElementAt(1419, MidElement).HLoc;
9332  ExitPair.second = Track->TrackElementAt(1420, MidElement).VLoc;
9333  Utilities->CallLogPop(2344);
9334  return(-1);
9335  }
9336  else //front element of train fully off the exit, one length to exit
9337  {
9338  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9339  {
9340  TempTTE = (Track->TrackElementAt(1421, MidElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9341  if(TempTTE < LastTimeToExit)
9342  {
9343  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9344  }
9345  else
9346  {
9347  TimeToExit = LastTimeToExit;
9348  }
9349  }
9350  else
9351  {
9352  TimeToExit = LastTimeToExit;
9353  }
9354  ExitPair.first = Track->TrackElementAt(1422, MidElement).HLoc;
9355  ExitPair.second = Track->TrackElementAt(1423, MidElement).VLoc;
9356  Utilities->CallLogPop(2345);
9357  return(-1);
9358  }
9359  }
9360  if(LeadElement > -1)
9361  {
9363  /* && (ExitSpeedFull > 1)*/) //LeadElement is the exit. If LeadMidLag have lead half on exit or if LeadMid have lead full on exit
9364  { //if LeadMidLag have 2.5 lengths to exit, else have 2 lengths to exit
9365  //ExitSpeedFull removed at v2.12.0 because of Cameron Neasom's error 28/01/22 - braking as entered continuation
9366  if(Straddle == LeadMidLag)
9367  {
9368  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9369  {
9370  TempTTE = (2.5 * Track->TrackElementAt(1426, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9371  if(TempTTE < LastTimeToExit)
9372  {
9373  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9374  }
9375  else
9376  {
9377  TimeToExit = LastTimeToExit;
9378  }
9379  }
9380  else
9381  {
9382  TimeToExit = LastTimeToExit;
9383  }
9384  ExitPair.first = Track->TrackElementAt(1427, LeadElement).HLoc;
9385  ExitPair.second = Track->TrackElementAt(1428, LeadElement).VLoc;
9386  Utilities->CallLogPop(2346);
9387  return(-1);
9388  }
9389  else
9390  {
9391  if(ExitSpeedFull > 0) //added at v2.12.0 because of Cameron Neasom's error 28/01/22
9392  {
9393  TempTTE = (2 * Track->TrackElementAt(1429, LeadElement).Length01) * 3.6 / 60 / ExitSpeedFull;
9394  if(TempTTE < LastTimeToExit)
9395  {
9396  TimeToExit = TempTTE; //else leave as is, don't want a sudden increase in time
9397  }
9398  else
9399  {
9400  TimeToExit = LastTimeToExit;
9401  }
9402  }
9403  else
9404  {
9405  TimeToExit = LastTimeToExit;
9406  }
9407  ExitPair.first = Track->TrackElementAt(1430, LeadElement).HLoc;
9408  ExitPair.second = Track->TrackElementAt(1431, LeadElement).VLoc;
9409  Utilities->CallLogPop(2347);
9410  return(-1);
9411  }
9412  }
9413  }
9414 //here if LeadElement > -1 and its forward connection also > -1
9415 
9416  // calc distance to next red signal
9417  if(!Stopped() || RevisedStoppedAtLoc())
9418  {
9419  int FirstPosToBeMeasured = Track->TrackElementAt(953, LeadElement).Conn[LeadExitPos];
9420  int FirstEntryPos = Track->TrackElementAt(954, LeadElement).ConnLinkPos[LeadExitPos];
9421  if((Straddle == LeadMidLag) && (TrainMode == Timetable))
9422 /* In TTMode it's important to set the first element to be measured ahead of the lead element only when the train fully on
9423  2 elements. Otherwise, if the train is only half on the lead element and approaching a station stop where the platform doesn't
9424  extend beyond the lead element stop point, the element ahead of the lead element is not a location whereas the ActionVector
9425  still points to the station stop location. In these circumstances the train hasn't yet stopped, so the dwell time at the
9426  stop isn't calculated, and the station to be stopped at isn't found as a future stop and nor are any other future stops
9427  because the ActionVector name never matches a future station. Hence all dwell times are omitted until the train lands fully
9428  on two elements. To avoid this when Straddle is LeadMidLag the first element to be measured is set to the lead element, so
9429  before the train has stopped the current station is still recognised as a future stop.
9430  In signaller mode stops don't count, and if pass stop signal command is given then when have LeadMidLag the current element
9431  becomes the signal, and the time to act indication becomes 'NOW'.
9432 */
9433  {
9434  FirstPosToBeMeasured = LeadElement;
9435  FirstEntryPos = LeadEntryPos;
9436  }
9437  float CurrentStopTime; // set to 0 at start of function
9438  float LaterStopTime; // set to 0 at start of function
9439  float RecoverableTime; // set to 0 at start of function
9440  int AvTrackSpeed; // set to zero at start of function
9441  bool SigControlAndCanPassRedSignal = ((TrainMode == Signaller) && AllowedToPassRedSignal);
9442  DistanceToRedSignal = TrainController->CalcDistanceToRedSignalandStopTime(0, FirstPosToBeMeasured, FirstEntryPos, SigControlAndCanPassRedSignal,
9443  ActionVectorEntryPtr, HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
9444 //at this point can't have both DistanceToRedSignal and DistanceToExit both set. Either both will be unset (-1) or one will be set.
9445 //Therefore since need to calculate the time for each in the same way use a generic
9446 
9447  if((DistanceToRedSignal == -1) && (DistanceToExit == -1))// both unset so no action needed
9448  {
9449  TimeToExit = -1;
9450  Utilities->CallLogPop(2076);
9451  return(-1);
9452  }
9453 //else one or other is set
9454  bool DistanceToRedSignalSet = (DistanceToRedSignal > -1);
9455  bool DistanceToExitSet = (DistanceToExit > -1);
9456  int GenericDistance = DistanceToRedSignal;
9457  if(DistanceToExitSet)
9458  {
9459  GenericDistance = DistanceToExit;
9460  }
9461 /* Have MinsDelayed; pos or neg,
9462  CurrentStopTime; pos or zero
9463  LaterStopTime; pos or zero
9464  RecoverableTime; pos or zero
9465 
9466  & from these calculate TotalStopTime. noting that:
9467  If stopped CurrentStopTime automatically adjusts for all early running and for as much late running as possible
9468  RecoverableTime always < LaterStopTime or both zero
9469  can't subtract more than RecoverableTime (MinsDelayed > 0)
9470  only subtract from LaterStopTime, not CurrentTime (MinsDelayed > 0)
9471  only subtract from LaterStopTime if LaterStopTime > 0 (MinsDelayed > 0)
9472  only add to LaterStopTime if LaterStopTime > 0 (MinsDelayed < 0)
9473  if running early & stopped at location CurrentStopTime will automatically include the excess
9474 */
9475  float TimeToSubtract, TotalStopTime;
9476  if(MinsDelayed > RecoverableTime)
9477  {
9478  TimeToSubtract = RecoverableTime;
9479  }
9480  else
9481  {
9482  TimeToSubtract = MinsDelayed; // may be negative;
9483  }
9484  if((AvTrackSpeed > 0) && (DistanceToStationStop <= GenericDistance) && (DistanceToStationStop > 0)) //protection against div by zero, not needed of no stop
9485  //before red signal, DistanceToStationStop != 0 as set to 0 if invalid
9486  //added at v2.6.1, DistanceToStationStop is calculated in SetTrainMovementValues, AvTrackSpeed is average to next red signal, but should be ok to use for
9487  //next station stop
9488  //after 2.7.0 changed (DistanceToStationStop < DistanceToRedSignal) to (DistanceToStationStop <= DistanceToRedSignal) because often have departure signal
9489  //next to the stop platform and if so the two distances are the same and the station stop time isn't included. Also after 2.7.0 used GetRepeatTime... to calc
9490  //departure time because ActionVectorEntryPtr->DepartureTime is the base time and therefore incorrect for repeats.
9491  //first find departure time from the next stop
9492  //at v2.9.0 changed MinsEarly calc to use ArrivalTime instead of departure time
9493  {
9494  if(ActionVectorEntryPtr->FormatType == TimeTimeLoc) //if already arrived then MinsEarly will be < 0 so becomes set to 0
9495  {
9498  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
9499  }
9500  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime != TDateTime(-1))) // not arrived yet
9501  {
9503  MinsEarly = (double(ArrivalTime - TrainController->TTClockTime) * 86400 / 60) - (DistanceToStationStop * 3.6 / 60 / AvTrackSpeed);
9504  if((ActionVectorEntryPtr + 1)->FormatType == TimeLoc)
9505  {
9506  // must be a departure
9507  DepartureTime = TrainController->GetRepeatTime(70, (ActionVectorEntryPtr + 1)->DepartureTime, RepeatNumber, IncrementalMinutes);
9508  }
9509  }
9510  else if((ActionVectorEntryPtr->FormatType == TimeLoc) && (ActionVectorEntryPtr->ArrivalTime == TDateTime(-1))) //already arrived
9511  {
9512  MinsEarly = 0;
9513  }
9514  if(MinsEarly < 0)
9515  {
9516  MinsEarly = 0;
9517  }
9518  }
9519  if(MinsDelayed < 0) // MinsDelayed < 0 means have arrived early at a station
9520  {
9521  if(CurrentStopTime > 0)
9522  {
9523  TotalStopTime = CurrentStopTime + LaterStopTime;
9524  }
9525  // stopped at loc, will depart on time
9526  else
9527  {
9528  TotalStopTime = LaterStopTime - MinsDelayed;
9529  }
9530  // not stopped, will depart on time at first later stop so add the delay
9531  }
9532  else if((MinsEarly > 0) && !Stopped()) //running early
9533  {
9534  TotalStopTime = LaterStopTime + MinsEarly;
9535  }
9536  else // on time or running late
9537  {
9538  if(LaterStopTime == 0)
9539  {
9540  TotalStopTime = CurrentStopTime;
9541  }
9542  // no later stops, if stopped now will depart as soon as possible,
9543  // if not stopped no stop times to add
9544  else
9545  {
9546  TotalStopTime = CurrentStopTime + LaterStopTime - TimeToSubtract; // later stops so deduct as much as can
9547  }
9548  }
9549  if(AvTrackSpeed < 30)
9550  {
9551  AvTrackSpeed = 30;
9552  }
9553  int Speed = AvTrackSpeed;
9554  if(AvTrackSpeed > int(MaxRunningSpeed))
9555  {
9556  Speed = int(MaxRunningSpeed);
9557  }
9558  if(TrainMode == Signaller)
9559  {
9560  Speed = SignallerMaxSpeed;
9561  TotalStopTime = 0;
9562  }
9563  if(DistanceToRedSignalSet)
9564  {
9565  TimeToAct = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
9566  // accel & decel taken into account in
9567  // CalcDistanceToRedSignalandStopTime
9568  // 3.6 converts Km/h to m/s & 60 converts seconds to minutes
9569  TimeToExit = -1;
9570  Utilities->CallLogPop(2079);
9571  return(TimeToAct);
9572  }
9573  else //DistanceToExitSet must be true
9574  {
9575  TimeToExit = TotalStopTime + GenericDistance * 3.6 / 60 / Speed;
9576  // accel & decel taken into account in
9577  // CalcDistanceToRedSignalandStopTime
9578  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
9579  Utilities->CallLogPop(2370);
9580  return(-1); //no red signal so no time to act
9581  }
9582  }
9583  else // stopped not at location
9584  {
9586  {
9587  TimeToAct = 0.0;
9588  TimeToExit = -1;
9589  }
9590  if(StoppedWithoutPower) //added at v2.13.2 as this situation was missed & time to act was 0 [If train failed then covered above]
9591  {
9592  TimeToAct = -1;
9593  TimeToExit = -1;
9594  }
9595  // but if stopped at a signal & autosigs route after it then ok, provided signal not failed
9596  if(StoppedAtSignal)
9597  {
9598  int NextElement = Track->TrackElementAt(928, LeadElement).Conn[LeadExitPos];
9599  int NextEntryPos = Track->TrackElementAt(929, LeadElement).ConnLinkPos[LeadExitPos];
9600  bool NextElementFailed = Track->TrackElementAt(1548, NextElement).Failed; //added at v2.13.2
9601  int NextExitPos;
9602  if(Track->TrackElementAt(930, NextElement).TrackType == Points)
9603  {
9604  if((NextEntryPos == 0) || (NextEntryPos == 2))
9605  // leading entry point
9606  {
9607  if(Track->TrackElementAt(931, NextElement).Attribute == 0)
9608  {
9609  NextExitPos = 1;
9610  }
9611  else
9612  {
9613  NextExitPos = 3;
9614  }
9615  }
9616  else
9617  {
9618  NextExitPos = 0; // trailing entry point
9619  }
9620  }
9621  else
9622  {
9623  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
9624  }
9625  int NextButOneElement = Track->TrackElementAt(932, NextElement).Conn[NextExitPos];
9626  int NextButOneEntryPos = Track->TrackElementAt(933, NextElement).ConnLinkPos[NextExitPos];
9627  int RouteNumber; // holder for referenced value, not used
9628  if((AllRoutes->GetRouteTypeAndNumber(32, NextButOneElement, NextButOneEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !NextElementFailed)
9629  { //NextElementFailed added at v2.13.2
9630  TimeToAct = -1;
9631  TimeToExit = -1;
9632  }
9633  }
9634  Utilities->CallLogPop(2074);
9635  return(TimeToAct);
9636  }
9637 }
9638 
9639 // ---------------------------------------------------------------------------
9640 
9642 {
9643  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainOnContinuation, " + HeadCode);
9644  if(LeadElement > -1)
9645  {
9647  {
9648  Utilities->CallLogPop(2148);
9649  return(true);
9650  }
9651  }
9652  if(MidElement > -1)
9653  {
9655  {
9656  Utilities->CallLogPop(2149);
9657  return(true);
9658  }
9659  }
9660  if(LagElement > -1)
9661  {
9663  {
9664  Utilities->CallLogPop(2150);
9665  return(true);
9666  }
9667  }
9668  Utilities->CallLogPop(2151);
9669  return(false);
9670 }
9671 
9672 // ---------------------------------------------------------------------------
9673 // TTrainController
9674 // ---------------------------------------------------------------------------
9675 
9677 {
9678  OnTimeArrivals = 0;
9679  LateArrivals = 0;
9680  EarlyArrivals = 0;
9681  OnTimePasses = 0;
9682  LatePasses = 0;
9683  EarlyPasses = 0;
9684  OnTimeExits = 0; //these 3 exits added at v2.9.2 - missed in error earlier
9685  LateExits = 0;
9686  EarlyExits = 0;
9687  OnTimeDeps = 0;
9688  LateDeps = 0;
9689  MissedStops = 0;
9690  OtherMissedEvents = 0;
9691  UnexpectedExits = 0;
9692  NumFailures = 0;
9693  IncorrectExits = 0;
9694  SPADEvents = 0;
9695  SPADRisks = 0;
9696  CrashedTrains = 0;
9697  Derailments = 0;
9698  TotArrDepPass = 0;
9699  TotLateArrMins = 0;
9700  TotEarlyArrMins = 0;
9701  TotLatePassMins = 0;
9702  TotEarlyPassMins = 0;
9703  TotLateExitMins = 0; //added at v2.9.1
9704  TotEarlyExitMins = 0; //added at v2.9.1
9705  TotLateDepMins = 0;
9706  ExcessLCDownMins = 0;
9707  SkippedTTEvents = 0; //added at v2.11.0
9708  TTClockTime = 0; // added for v0.6
9710  // added at v1.3.0 to ensure false at start
9711  OpTimeToActUpdateCounter = 0; // new v2.2.0
9712  OpActionPanelVisible = false; // new v2.2.0
9713  // reset all message flags, stops them being given twice (shouldn't be needed here but add for safety) //new at v2.4.0
9714  SSHigh = false;
9715  MRSHigh = false;
9716  MRSLow = false;
9717  MassHigh = false;
9718  BFHigh = false;
9719  BFLow = false;
9720  PwrHigh = false;
9721  SigSHigh = false;
9722  SigSLow = false;
9723  randomize();
9724  // to seed rand() & random() with a random number (see UpdateTrain)
9725 }
9726 
9727 // ---------------------------------------------------------------------------
9728 
9730 {
9731  for(unsigned int x = 0; x < TrainVector.size(); x++)
9732  {
9733  TrainVectorAt(32, x).DeleteTrain(4);
9734  }
9735  TrainVector.clear();
9736 }
9737 
9738 // ---------------------------------------------------------------------------
9739 
9740 void TTrainController::LogEvent(AnsiString Str)
9741 {
9742  AnsiString FullStr = Utilities->TimeStamp() + "," + TTClockTime.FormatString("hh:nn:ss") + "," + Str;
9743 
9744  // restrict to last 1000 entries
9745  Utilities->EventLog.push_back(FullStr);
9746  if(Utilities->EventLog.size() > 1000)
9747  {
9748  Utilities->EventLog.pop_front();
9749  }
9750 }
9751 
9752 // ---------------------------------------------------------------------------
9753 
9755 {
9756  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Operate");
9757  bool ClockState = Utilities->Clock2Stopped;
9758  Utilities->Clock2Stopped = true;
9759  // new section dealing with Snt & Snt-sh additions
9760  // BUT don't add trains if points or route flashing [conditions added for Version 0.6 as a result of Najamuddin's error - 15/01/11] - wait until next
9761  // clock tick after stops flashing
9763  {
9764  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
9765  {
9766  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
9767  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
9768  TActionEventType EventType = NoEvent;
9769  if(AVEntry0.Command == "Snt")
9770  {
9771  // calc below only for Snt & Snt-sh entries rather than all entries to save time
9772  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
9773  int IncrementalMinutes = 0;
9774  int IncrementalDigits = 0;
9775  if(AVEntryLast.FormatType == Repeat)
9776  {
9777  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
9778  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
9779  }
9780  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
9781  {
9782  throw Exception("Error - Repeat entry && less than two trains for Snt entry: " + TDEntry.HeadCode);
9783  }
9784  // see above note
9785 
9786  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
9787  {
9788  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
9789  if(TTOD.RunningEntry != NotStarted)
9790  {
9791  continue;
9792  }
9793 
9794 //Multiplayer: here check for a train entering at a coupling {RearStartOrRepeatMins shows if it's a coupling or not), and can only be a Snt entry
9795 //if so and no arrival signalled yet bypass the timetabled arrival
9796 //if so and arrival signalled then start the new service, using the repeat number and headcode for the entering train
9797 //if a repeat is skipped then should be ok if it arrives later as its RunningEntry is still NotStarted
9798 
9799  if(GetRepeatTime(2, AVEntry0.EventTime, y, IncrementalMinutes) > TTClockTime)
9800  {
9801  break; // all the rest will also be greater
9802  }
9803  AnsiString TrainHeadCode = GetRepeatHeadCode(22, TDEntry.HeadCode, y, IncrementalDigits);
9804  if(AddTrain(2, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TrainHeadCode, TDEntry.StartSpeed, TDEntry.Mass,
9805  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, y, IncrementalMinutes, IncrementalDigits,
9806  TDEntry.SignallerSpeed, AVEntry0.SignallerControl, EventType))
9807  {
9808  TTOD.TrainID = TrainVector.back().TrainID;
9809  TTOD.RunningEntry = Running;
9810  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
9811  }
9812  else if(EventType == FailTrainEntry)
9813  {
9814  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
9815  }
9816  }
9817  }
9818  if(AVEntry0.Command == "Snt-sh")
9819  // just start this once, shuttle repeats take care of restarts
9820  {
9821  // calc below only for Snt & Snt-sh entries rather than all entries to save time
9822  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
9823  int IncrementalMinutes = 0;
9824  int IncrementalDigits = 0;
9825  if(AVEntryLast.FormatType == Repeat)
9826  {
9827  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
9828  IncrementalDigits = AVEntryLast.FrontStartOrRepeatDigits;
9829  }
9830  if((AVEntryLast.FormatType == Repeat) && (TDEntry.NumberOfTrains < 2))
9831  {
9832  throw Exception("Error - Repeat entry && less than two trains for Snt-sh entry: " + TDEntry.HeadCode);
9833  }
9834  // see above note
9835  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(0);
9836  if(TTOD.RunningEntry == NotStarted)
9837  {
9838  if(AVEntry0.EventTime <= TTClockTime)
9839  {
9840  if(AddTrain(3, AVEntry0.RearStartOrRepeatMins, AVEntry0.FrontStartOrRepeatDigits, TDEntry.HeadCode, TDEntry.StartSpeed, TDEntry.Mass,
9841  TDEntry.MaxRunningSpeed, TDEntry.MaxBrakeRate, TDEntry.PowerAtRail, "Timetable", &TDEntry, 0, IncrementalMinutes, IncrementalDigits,
9842  TDEntry.SignallerSpeed, false, EventType))
9843  // false for SignallerControl
9844  {
9845  TTOD.TrainID = TrainVector.back().TrainID;
9846  TTOD.RunningEntry = Running;
9847  TrainVector.back().Description = TDEntry.FixedDescription; //added at v2.16.1
9848  }
9849  else if(EventType == FailTrainEntry)
9850  {
9851  break; // if a train can't enter no point checking any more repeats as they won't be able to enter either
9852  }
9853  }
9854  }
9855  }
9856  }
9857  }
9858 
9859  // deal with running trains but abort if any vectors added, would probably be OK but don't risk a vector reallocation disrupting the
9860  // iteration, next cycle will catch up with any other pending updates
9861  if(!TrainVector.empty())
9862  {
9863  TrainAdded = false;
9864 
9865 //elapsed time investigations
9866 
9867 //elapsed time segment
9868 //double Start, End;
9869 //AnsiString ElapsedTimeReport = "";
9870 //end elasped time segment
9871  AllRoutes->CallonVector.clear(); // this will be rebuilt during the calls to UpdateTrain
9872 //elapsed time segment
9873 //PerfLogForm->PerformanceLog(-1, "\n Train vector size: " + AnsiString(TrainVector.size()) + '\n');
9874 //PerfLogForm->PerformanceLog(-1, "Start time list");
9875 //end elapsed time segment
9876  for(unsigned int x = 0; x < TrainVector.size(); x++)
9877  {
9878 //elapsed time segment
9879 //Start = double(GetTime()) * 86400; //secs
9880 //end elapsed time segment
9881  TrainVectorAt(33, x).UpdateTrain(0);
9882 //elapsed time segment
9883 //End = double(GetTime()) * 86400;
9884 //ElapsedTimeReport = TrainVectorAt(-1, x).TrainDataEntryPtr->ServiceReference + AnsiString(" ") + AnsiString(int((End - Start) * 1000)); //msecs
9885 //PerfLogForm->PerformanceLog(-1, ElapsedTimeReport);
9886 //end elapsed time segment
9887 
9888 //end elapsed time investigations
9889 
9890  /* added HasTrainGone() condition below in v0.4c to prevent 2 trains both having TrainGone set in UpdateTrain
9891  at the same time. That caused the error Craig Weekes reported in November 2010 where 2 trains exited at the same time, and later the TrainVector
9892  iterates in reverse to erase the second train to have gone (when the first train to have gone comes before the second in TrainVector),
9893  but afterwards ReplotTrains iterates forwards and therefore replots the first train to have gone and therefore sets the TrainIDOnElement value
9894  to the exited train, with nothing to reset it. Hovering the mouse over that element with train information enabled causes an error because
9895  the track element thinks the train is still there, whereas it is missing from the TrainVector. BUT subsequently (in v2.11.1) changed RePlotTrains
9896  so it doesn't plot trains with TrainGone set, but left this is as does no harm
9897 
9898  Had another error notified by Kevin Smith on 02/01/22 where a train was manually removed in the same clock cycle as a train exited, and this caused
9899  the same error as above. Did a lot of experimenting but eventually cured it with two changes, first as above in RePlotTrains, and also below adding
9900  a break; command after one TrainHasGone() dealt with. There were introduced in v2.11.1 & seems ok now
9901 
9902  These changes should deal with any number of TrainGone flags set in the same clock cycle - from exiting, manual removal, or joins
9903  */
9904  if(TrainAdded || TrainVectorAt(35, x).HasTrainGone())
9905  {
9906  break; //only one exited train will be dealt with at a time (see below) so no point looking further
9907  }
9908  }
9909  // set warning flags (ManualLCDownAttentionWarning dealt with in InterfaceUnit)
9910  CrashWarning = false;
9911  DerailWarning = false;
9912  SPADWarning = false;
9913  CallOnWarning = false;
9914  SignalStopWarning = false;
9915  BufferAttentionWarning = false;
9916  TrainFailedWarning = false;
9917  for(int x = TrainVector.size() - 1; x >= 0; x--) // reverse because of erase
9918  {
9919  TTrain &Train = TrainVectorAt(34, x);
9920  if(Train.Crashed)
9921  // can't use background colours for crashed & derailed because same colour
9922  {
9923  CrashWarning = true;
9924  }
9925  else if(Train.Derailed)
9926  // can't use background colours for crashed & derailed because same colour
9927  {
9928  DerailWarning = true;
9929  }
9930  else if(Train.BackgroundColour == clSPADBackground)
9931  // use colour as that changes as soon as passes signal
9932  {
9933  SPADWarning = true;
9934  }
9935  else if(Train.BackgroundColour == clTrainFailedBackground)
9936  {
9937  TrainFailedWarning = true;
9938  }
9939  else if(Train.BackgroundColour == clCallOnBackground)
9940  // use colour as also stopped at signal
9941  {
9942  CallOnWarning = true;
9943  }
9944  else if(Train.BackgroundColour == clSignalStopBackground)
9945  // use colour to distinguish from call-on
9946  {
9947  SignalStopWarning = true;
9948  }
9949  else if(Train.BackgroundColour == clBufferAttentionNeeded)
9950  // use colour to distinguish from ordinary buffer stop
9951  {
9952  BufferAttentionWarning = true;
9953  }
9954  if(Train.HasTrainGone())
9955  {
9956  AnsiString Loc = "";
9957  bool ElementFound = false;
9958  TTrackElement TE;
9959  if(Train.LagElement > -1)
9960  {
9961  TE = Track->TrackElementAt(531, Train.LagElement);
9962  ElementFound = true;
9963  }
9964  else if(Train.MidElement > -1)
9965  {
9966  TE = Track->TrackElementAt(779, Train.MidElement);
9967  ElementFound = true;
9968  }
9969  else if(Train.LeadElement > -1)
9970  {
9971  TE = Track->TrackElementAt(780, Train.LeadElement);
9972  ElementFound = true;
9973  }
9974  if(ElementFound)
9975  {
9976  if(TE.ActiveTrackElementName != "")
9977  {
9978  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
9979  }
9980  else
9981  {
9982  Loc = "track element " + TE.ElementID;
9983  }
9984  }
9985  TActionVectorEntry *AVEntryPtr = Train.ActionVectorEntryPtr;
9986  if((Train.SignallerRemoved) || (Train.JoinedOtherTrainFlag))
9987  // need above first because may also have ActionVectorEntryPtr == "Fer"
9988  {
9989  Train.UnplotTrain(9);
9990  // added at v1.3.0 to reset signals after train removed from an autosigsroute
9992  {
9995  }
9996  // end of addition
9997  AllRoutes->RebuildRailwayFlag = true;
9998  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode, to replot LCs
9999  // correctly after a crash
10000  }
10001  else if(AVEntryPtr->Command == "Fer")
10002  {
10003  bool CorrectExit = false;
10004  if(!AVEntryPtr->ExitList.empty())
10005  {
10006  for(TNumListIterator ELIT = AVEntryPtr->ExitList.begin(); ELIT != AVEntryPtr->ExitList.end(); ELIT++)
10007  {
10008  if(*ELIT == Train.LagElement)
10009  {
10010  CorrectExit = true;
10011  }
10012  }
10013  }
10014  if(CorrectExit)
10015  {
10016  Train.LogAction(19, Train.HeadCode, "", Leave, Loc, "", AVEntryPtr->EventTime, AVEntryPtr->Warning);
10017  }
10018  else
10019  {
10020  LogActionError(38, Train.HeadCode, "", FailIncorrectExit, Loc);
10021  }
10022  }
10023  else
10024  {
10025  if(!AVEntryPtr->SignallerControl)
10026  {
10027  LogActionError(26, Train.HeadCode, "", FailUnexpectedExitRailway, Loc);
10028  Train.SendMissedActionLogs(2, -2, AVEntryPtr);
10029  // -2 is marker for send messages for all remaining actions except Fer if present
10030  }
10031  else
10032  {
10033  Train.LogAction(31, Train.HeadCode, "", SignallerLeave, Loc, "", TDateTime(0), false); // false for Warning
10034  }
10035  }
10036  Utilities->CumulativeDelayedRandMinsAllTrains += Train.CumulativeDelayedRandMinsOneTrain; //added at v2.13.0 for random delays
10037  Train.TrainDataEntryPtr->TrainOperatingDataVector.at(Train.RepeatNumber).RunningEntry = Exited;
10038  Train.DeleteTrain(1);
10039  TrainVector.erase(TrainVector.begin() + x);
10040  ReplotTrains(1, Display); //to reset ElementIDs for remaining trains when have removed a train
10041  //NB: won't plot any trains with TrainGone flag set (changed at v2.11.1)
10042  break; //added at v2.11.1 to ensure that only one train with TrainGone set is dealt with in one clock cycle
10043  }
10044  }
10045  }
10046  else
10047  {
10048  // reset all flags in case last train removed with flag set
10049  CrashWarning = false;
10050  DerailWarning = false;
10051  SPADWarning = false;
10052  CallOnWarning = false;
10053  SignalStopWarning = false;
10054  BufferAttentionWarning = false;
10055  TrainFailedWarning = false;
10056  }
10057  // update OpTimeToActMultimap
10059  {
10061  // clears entries then adds values for running trains then for continuation entries
10063  //added for multiplayer for running trains only
10064  }
10065  Utilities->Clock2Stopped = ClockState;
10066  Utilities->CallLogPop(723);
10067 }
10068 
10069 // ---------------------------------------------------------------------------
10071 {
10072  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",FinishedOperation");
10073  if(!TrainVector.empty())
10074  {
10075  for(int x = TrainVector.size() - 1; x >= 0; x--)
10076  {
10077  TrainVectorAt(50, x).DeleteTrain(2);
10078  }
10079  TrainVector.clear();
10080  }
10081  if(!TrainDataVector.empty())
10082  {
10083  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
10084  {
10085  TTrainDataEntry &TDEntry = TrainDataVector.at(x);
10086  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
10087  {
10088  TTrainOperatingData &TOD = TDEntry.TrainOperatingDataVector.at(y);
10089  TOD.RunningEntry = NotStarted;
10090  TOD.TrainID = -1;
10091  TOD.EventReported = NoEvent;
10092  }
10093  }
10094  }
10095  Display->GetOutputLog1()->Caption = "";
10096  Display->GetOutputLog2()->Caption = "";
10097  Display->GetOutputLog3()->Caption = "";
10098  Display->GetOutputLog4()->Caption = "";
10099  Display->GetOutputLog5()->Caption = "";
10100  Display->GetOutputLog6()->Caption = "";
10101  Display->GetOutputLog7()->Caption = "";
10102  Display->GetOutputLog8()->Caption = "";
10103  Display->GetOutputLog9()->Caption = "";
10104  Display->GetOutputLog10()->Caption = "";
10105  Utilities->CallLogPop(1352);
10106 }
10107 
10108 // ---------------------------------------------------------------------------
10109 
10111 {
10112  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ReplotTrains");
10113  if(!TrainVector.empty())
10114  {
10115  for(unsigned int x = 0; x < TrainVector.size(); x++)
10116  {
10117  if(!TrainVectorAt(84, x).HasTrainGone()) //added at v2.11.0 to prevent plotting a train pending removal & particularly to prevent TrainElementID's being reinstated
10118  { //see Kevin Smith error information for details
10119  TrainVectorAt(51, x).PlotTrain(4, Disp);
10120  }
10121  }
10122  }
10123  Utilities->CallLogPop(724);
10124 }
10125 
10126 // ---------------------------------------------------------------------------
10127 
10128 void TTrainController::WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
10129 {
10130  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WriteTrainsToImage");
10131  if(!TrainVector.empty())
10132  {
10133  for(unsigned int x = 0; x < TrainVector.size(); x++)
10134  {
10135  TrainVectorAt(61, x).WriteTrainToImage(0, Bitmap);
10136  }
10137  }
10138  Utilities->CallLogPop(1707);
10139 }
10140 
10141 // ---------------------------------------------------------------------------
10142 
10144 {
10145  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",UnplotTrains");
10146  if(!TrainVector.empty())
10147  {
10148  for(unsigned int x = 0; x < TrainVector.size(); x++)
10149  {
10150  TrainVectorAt(52, x).UnplotTrain(10);
10151  }
10152  }
10154  Utilities->CallLogPop(725);
10155 }
10156 
10157 // ---------------------------------------------------------------------------
10158 
10159 bool TTrainController::AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed,
10160  double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes,
10161  int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
10162 {
10163  LogEvent(AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) + "," + HeadCode + "," + AnsiString(StartSpeed) +
10164  "," + AnsiString(Mass) + "," + ModeStr);
10165  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",AddTrain," + AnsiString(RearPosition) + "," + AnsiString(FrontPosition) +
10166  "," + HeadCode + "," + AnsiString(StartSpeed) + "," + AnsiString(Mass) + "," + ModeStr); //at v2.11.1 dropped later headcode - was listed twice
10167 
10168  int RearExitPos = -1;
10169 
10170  for(int x = 0; x < 4; x++)
10171  {
10172  if(Track->TrackElementAt(519, RearPosition).Conn[x] == FrontPosition)
10173  {
10174  RearExitPos = x;
10175  }
10176  }
10177  if(RearExitPos == -1)
10178  {
10179  throw Exception("Error, RearExit == -1 in AddTrain");
10180  }
10181  bool ReportFlag = true;
10182 
10183  // used to stop repeated messages from CheckStartAllowable when split failed
10184  if(TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported != NoEvent)
10185  {
10186  ReportFlag = false;
10187  }
10188  if(!CheckStartAllowable(0, RearPosition, RearExitPos, HeadCode, ReportFlag, EventType))
10189  {
10190  // messages sent to performance log in CheckStartAllowable if ReportFlag true
10191  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = EventType;
10192  Utilities->CallLogPop(938);
10193  return(false);
10194  }
10195  TrainDataEntryPtr->TrainOperatingDataVector.at(RepeatNumber).EventReported = NoEvent;
10196  TTrainMode TrainMode = NoMode;
10197 
10198  if(ModeStr == "Timetable")
10199  {
10200  TrainMode = Timetable;
10201  }
10202  // all else gives 'None', 'Signaller' set within program
10203 
10204  if(MaxRunningSpeed < 10)
10205  {
10206  MaxRunningSpeed = 10; // added at v0.6 to avoid low max speeds
10207  }
10208  if(SignallerSpeed < 10)
10209  {
10210  SignallerSpeed = 10; // added at v0.6 to avoid low max speeds
10211  }
10212  TTrain *NewTrain = new TTrain(0, RearPosition, RearExitPos, HeadCode, StartSpeed, Mass, MaxRunningSpeed, MaxBrakeRate, PowerAtRail, TrainMode,
10213  TrainDataEntryPtr, RepeatNumber, IncrementalMinutes, IncrementalDigits, SignallerSpeed);
10214 
10215  LogEvent("AddTrainSupplemental: Service Ref = " + TrainDataEntryPtr->ServiceReference + ", TrainID = " + AnsiString(NewTrain->TrainID)); //new at v2.11.1 so can relate headcode to ID
10216 
10217  NewTrain->ActionVectorEntryPtr = &(TrainDataEntryPtr->ActionVector.at(0));
10218  // initialise here rather than in TTrain constructor as create trains
10219  // with Null TrainDataEntryPtr when loading session trains
10220  if(SignallerControl)
10221  {
10222  NewTrain->TimetableFinished = true;
10223  NewTrain->SignallerStoppingFlag = false;
10224  NewTrain->TrainMode = Signaller;
10225  if(NewTrain->MaxRunningSpeed > NewTrain->SignallerMaxSpeed)
10226  {
10227  NewTrain->MaxRunningSpeed = NewTrain->SignallerMaxSpeed;
10228  }
10230  }
10231  // deal with starting conditions:-
10232  // unlocated Snt: just report entry & advance pointer
10233  // located Snt or Sfs: set station conditions as would if had reached stop point in Update(), & advance the ActionVectorEntryPtr
10234  // Sns doesn't need a new train
10235  if(NewTrain->ActionVectorEntryPtr->LocationName != "")
10236  // covers all above located starts
10237  // if location of Snt was a station (that is set as LocationName, i.e. not just any station) that isn't next departure station then
10238  // wouldn't have accepted the timetable
10239  {
10240  // first check if LeadElement (can't access LeadElement directly yet as not set, use FrontPosition instead) is buffers, note that
10241  // StoppedAtBuffers is set in UpdateTrain()
10242  if(Track->TrackElementAt(520, FrontPosition).TrackType == Buffers)
10243  // buffer end must be ahead of train or would have failed start position check
10244  {
10245  NewTrain->StoppedAtLocation = true;
10246  NewTrain->PlotStartPosition(0);
10248  NewTrain->LogAction(20, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10249  NewTrain->ActionVectorEntryPtr->Warning);
10250  if(!SignallerControl) // don't advance if SignalControlEntry
10251  {
10252  NewTrain->ActionVectorEntryPtr++;
10253  // should be a command, could be a location departure but if so can't depart so set 'Hold' anyway
10254  }
10255  NewTrain->LastActionTime = TTClockTime;
10256  }
10257  // else a through station stop
10258  else
10259  {
10260  NewTrain->StoppedAtLocation = true;
10261  NewTrain->PlotStartPosition(10);
10263  NewTrain->LogAction(21, NewTrain->HeadCode, "", Create, NewTrain->ActionVectorEntryPtr->LocationName, "", NewTrain->ActionVectorEntryPtr->EventTime,
10264  NewTrain->ActionVectorEntryPtr->Warning);
10265  if(!SignallerControl) // don't advance if SignalControlEntry
10266  {
10267  NewTrain->ActionVectorEntryPtr++;
10268  }
10269  NewTrain->LastActionTime = TTClockTime;
10270  }
10271  }
10272  else // unlocated entry (i.e. not a stop entry, but could still be at a named location)
10273  {
10274  NewTrain->PlotStartPosition(11);
10275  TTrackElement TE = Track->TrackElementAt(530, NewTrain->RearStartElement);
10276  AnsiString Loc = "";
10277  if(TE.ActiveTrackElementName != "")
10278  {
10279  Loc = TE.ActiveTrackElementName + ", track element " + TE.ElementID;
10280  }
10281  else
10282  {
10283  Loc = "track element " + TE.ElementID;
10284  }
10285  if(TE.TrackType == Continuation)
10286  {
10287  NewTrain->LogAction(22, NewTrain->HeadCode, "", Enter, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10288  }
10289  else
10290  {
10291  NewTrain->LogAction(23, NewTrain->HeadCode, "", Create, Loc, "", NewTrain->ActionVectorEntryPtr->EventTime, NewTrain->ActionVectorEntryPtr->Warning);
10292  }
10293  if(!SignallerControl) // don't advance if SignalControlEntry
10294  {
10295  NewTrain->ActionVectorEntryPtr++;
10296  }
10297  NewTrain->LastActionTime = TTClockTime;
10298  // no need to set LastActionTime for an unlocated entry
10299  }
10300  // cancel a wrong-direction route if either element of train starts on one
10301  if(NewTrain->LeadElement > -1)
10302  {
10303  NewTrain->CheckAndCancelRouteForWrongEndEntry(3, NewTrain->LeadElement, NewTrain->LeadEntryPos);
10304  }
10305  if(NewTrain->MidElement > -1)
10306  {
10307  NewTrain->CheckAndCancelRouteForWrongEndEntry(4, NewTrain->MidElement, NewTrain->MidEntryPos);
10308  }
10309  // set signals for a right-direction autosigs route for either element of train on one
10310  // erase elements back to start for a non-autosigs route & check if an autosigs route immediately behind it, and if so set its signals
10311  // note that all but autosigs routes become part of a single route, so there can only be an autosigs route behind the non-autosigs route
10312  int RouteNumber = -1;
10313  bool SignalsSet = false;
10314 
10315  if(NewTrain->LeadElement > -1)
10316  {
10317  if(AllRoutes->GetRouteTypeAndNumber(13, NewTrain->LeadElement, NewTrain->LeadEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10318  {
10319  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10320  int RouteStartPosition;
10321  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10322  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(21, Track->TrackElementAt(955, FrontPosition).HLoc,
10323  Track->TrackElementAt(956, FrontPosition).VLoc, SecondPair);
10324  if(FirstPair.first == RouteNumber)
10325  {
10326  RouteStartPosition = FirstPair.second;
10327  }
10328  else if(SecondPair.first == RouteNumber)
10329  {
10330  RouteStartPosition = SecondPair.second;
10331  }
10332  else
10333  {
10334  throw Exception("Error, RouteNumber not found in Route2MultiMap in 1st of 2 calls to SetAllRearwardsSignals in AddTrain");
10335  }
10336  AllRoutes->SetAllRearwardsSignals(10, 0, RouteNumber, RouteStartPosition);
10337  SignalsSet = true;
10338  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10339  }
10340  else if(RouteNumber > -1) // non-autosigsroute
10341  {
10342  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(181, RouteNumber).GetFixedPrefDirElementAt(194, 0);
10343  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10344  int FirstELinkPos = TempPDE.GetELinkPos();
10345  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->LeadElement))
10346  {
10347  AllRoutes->RemoveRouteElement(16, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10348  TempPDE = AllRoutes->GetFixedRouteAt(182, RouteNumber).GetFixedPrefDirElementAt(195, 0);
10349  }
10350  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->LeadElement))
10351  {
10352  AllRoutes->RemoveRouteElement(17, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10353  // remove the last element under LeadElement
10354  }
10355  AllRoutes->RebuildRailwayFlag = true;
10356  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10357  // now deal with a rear linked autosigs route
10358  if(Track->TrackElementAt(820, FirstTVPos).Conn[FirstELinkPos] > -1)
10359  {
10360  int LinkedRouteNumber = -1;
10361  if(AllRoutes->GetRouteTypeAndNumber(17, Track->TrackElementAt(821, FirstTVPos).Conn[FirstELinkPos],
10362  Track->TrackElementAt(822, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10363  {
10364  AllRoutes->GetFixedRouteAt(169, LinkedRouteNumber).SetRouteSignals(0);
10365  // this is ok as here we are setting signals from the start of the route
10366  }
10367  }
10368  SignalsSet = true;
10369  }
10370  }
10371  if(NewTrain->MidElement > -1)
10372  // if entering at a continuation MidElement == -1
10373  {
10374  // this is included in case a train starts with LeadElement on no route and MidElement on a route
10375  if(!SignalsSet)
10376  {
10377  RouteNumber = -1;
10378  if(AllRoutes->GetRouteTypeAndNumber(14, NewTrain->MidElement, NewTrain->MidEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute)
10379  {
10380  // below added in place of SetRouteSignals in v2.4.0 as don't want to set signals from start of route for a new train addition
10381  int RouteStartPosition;
10382  TAllRoutes::TRouteElementPair FirstPair, SecondPair;
10383  FirstPair = AllRoutes->GetRouteElementDataFromRoute2MultiMap(22, Track->TrackElementAt(957, RearPosition).HLoc,
10384  Track->TrackElementAt(958, RearPosition).VLoc, SecondPair);
10385  if(FirstPair.first == RouteNumber)
10386  {
10387  RouteStartPosition = FirstPair.second;
10388  }
10389  else if(SecondPair.first == RouteNumber)
10390  {
10391  RouteStartPosition = SecondPair.second;
10392  }
10393  else
10394  {
10395  throw Exception("Error, RouteNumber not found in Route2MultiMap in 2nd of 2 calls to SetAllRearwardsSignals in AddTrain");
10396  }
10397  AllRoutes->SetAllRearwardsSignals(11, 0, RouteNumber, RouteStartPosition);
10398  SignalsSet = true;
10399  // AllRoutes->GetFixedRouteAt(, RouteNumber).SetRouteSignals(); above substituted in v2.4.0
10400  }
10401  else if(RouteNumber > -1) // non-autosigsroute
10402  {
10403  TPrefDirElement TempPDE = AllRoutes->GetFixedRouteAt(184, RouteNumber).GetFixedPrefDirElementAt(196, 0);
10404  int FirstTVPos = TempPDE.GetTrackVectorPosition();
10405  int FirstELinkPos = TempPDE.GetELinkPos();
10406  while(TempPDE.GetTrackVectorPosition() != (unsigned int)(NewTrain->MidElement))
10407  {
10408  AllRoutes->RemoveRouteElement(18, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10409  TempPDE = AllRoutes->GetFixedRouteAt(185, RouteNumber).GetFixedPrefDirElementAt(197, 0);
10410  }
10411  if(TempPDE.GetTrackVectorPosition() == (unsigned int)(NewTrain->MidElement))
10412  {
10413  AllRoutes->RemoveRouteElement(19, TempPDE.HLoc, TempPDE.VLoc, TempPDE.GetELink());
10414  // remove the last element under LeadElement
10415  }
10416  AllRoutes->RebuildRailwayFlag = true;
10417  // to force ClearandRebuildRailway at next clock tick if not in zoom-out mode
10418  // now deal with a rear linked autosigs route
10419  if(Track->TrackElementAt(823, FirstTVPos).Conn[FirstELinkPos] > -1)
10420  {
10421  int LinkedRouteNumber = -1;
10422  if(AllRoutes->GetRouteTypeAndNumber(19, Track->TrackElementAt(824, FirstTVPos).Conn[FirstELinkPos],
10423  Track->TrackElementAt(825, FirstTVPos).ConnLinkPos[FirstELinkPos], LinkedRouteNumber) == TAllRoutes::AutoSigsRoute)
10424  {
10425  AllRoutes->GetFixedRouteAt(170, LinkedRouteNumber).SetRouteSignals(1);
10426  // this is ok as now we are setting signals from the start of the route
10427  }
10428  }
10429  }
10430  }
10431  }
10432  TrainVector.push_back(*NewTrain);
10433  Utilities->CallLogPop(731);
10434  return(true);
10435 }
10436 
10437 // ---------------------------------------------------------------------------
10438 
10439 int TTrainController::EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
10440 {
10441  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",EntryPos," + AnsiString(TrainIDIn) + "," +
10442  AnsiString(TrackVectorNumber));
10443  int VecPos = -1;
10444 
10445  for(unsigned int x = 0; x < TrainVector.size(); x++)
10446  {
10447  if(TrainVectorAt(1, x).TrainID == TrainIDIn)
10448  {
10449  VecPos = x;
10450  }
10451  }
10452  if(VecPos == -1)
10453  {
10454  throw Exception("Error, VecPos not set in EntryPos");
10455  }
10456  if(TrainVectorAt(2, VecPos).LeadElement == TrackVectorNumber)
10457  {
10458  Utilities->CallLogPop(734);
10459  return(TrainVectorAt(3, VecPos).LeadEntryPos);
10460  }
10461  else if(TrainVectorAt(4, VecPos).MidElement == TrackVectorNumber)
10462  {
10463  Utilities->CallLogPop(735);
10464  return(TrainVectorAt(5, VecPos).MidEntryPos);
10465  }
10466  else if(TrainVectorAt(6, VecPos).LagElement == TrackVectorNumber)
10467  {
10468  Utilities->CallLogPop(736);
10469  return(TrainVectorAt(7, VecPos).LagEntryPos);
10470  }
10471  Utilities->CallLogPop(737);
10472  return(-1);
10473 }
10474 
10475 // ---------------------------------------------------------------------------
10476 
10478 {
10479  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAtIdent," + AnsiString(TrainID));
10480  for(unsigned int x = 0; x < TrainVector.size(); x++)
10481  {
10482  if(TrainVectorAt(53, x).TrainID == TrainID)
10483  {
10484  Utilities->CallLogPop(738);
10485  return(TrainVectorAt(54, x));
10486  }
10487  }
10488  throw Exception("Error - No Train identified in TrainVectorAtIdent with ID = " + AnsiString(TrainID));
10489 }
10490 
10491 // ---------------------------------------------------------------------------
10492 
10493 bool TTrainController::TrainExistsAtIdent(int Caller, int TrainID)
10494 // return true if find the train (added at v2.4.0 as can select a removed train in
10495 // ActionsDueListBox before it updates - reported by LiWinDom in error report 23/04/20)
10496 {
10497  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainExistsAtIdent," + AnsiString(TrainID));
10498  for(unsigned int x = 0; x < TrainVector.size(); x++)
10499  {
10500  if(TrainVectorAt(69, x).TrainID == TrainID)
10501  {
10502  Utilities->CallLogPop(2152);
10503  return(true);
10504  }
10505  }
10506  Utilities->CallLogPop(2153);
10507  return(false);
10508 }
10509 
10510 // ---------------------------------------------------------------------------
10511 
10512 TDateTime TTrainController::GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
10513 {
10514  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetControllerTrainTime," + AnsiString(RepeatNumber) + "," +
10515  Utilities->Format96HHMMSS(Time));
10516  TDateTime RepeatTime = TrainController->GetRepeatTime(47, Time, RepeatNumber, IncrementalMinutes);
10517 
10518  Utilities->CallLogPop(2061);
10519  return(RepeatTime);
10520 }
10521 
10522 // ---------------------------------------------------------------------------
10523 
10524 AnsiString TTrainController::ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepNum, int IncMins, int IncDig)
10525 // Enter with Ptr pointing to first action to be listed (i.e. next action)
10526 {
10527  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ContinuationEntryFloatingTTString" + "," + TTDEPtr->HeadCode);
10528  AnsiString RetStr = "", PartStr = "";
10529  int Count = 0;
10530  TActionVectorIterator Ptr = TTDEPtr->ActionVector.begin();
10531 
10532  Ptr--; // because incremented at start of loop
10533  do
10534  {
10535  Ptr++;
10536  if((Ptr->Command != "") && (Ptr->Command[1] == 'S'))
10537  {
10538  continue; // move past the starting entry
10539  }
10540  if((Ptr->FormatType == Repeat) || Ptr >= TTDEPtr->ActionVector.end())
10541  {
10542  break;
10543  }
10544  if(Ptr->SignallerControl)
10545  {
10546  RetStr = "Train under signaller control";
10547  break;
10548  }
10549  if(Ptr->FormatType == TimeTimeLoc)
10550  {
10551  if(Ptr->ArrivalTime == Ptr->DepartureTime)
10552  {
10553  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(0, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive & depart from " + Ptr->LocationName;
10554  }
10555  else
10556  {
10557  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(1, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName + '\n' +
10558  Utilities->Format96HHMM(GetControllerTrainTime(2, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
10559  Count++; // because there are 2 entries
10560  }
10561  }
10562  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime != TDateTime(-1)))
10563  {
10564  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(3, Ptr->ArrivalTime, RepNum, IncMins)) + ": Arrive at " + Ptr->LocationName;
10565  }
10566  else if((Ptr->FormatType == TimeLoc) && (Ptr->ArrivalTime == TDateTime(-1)))
10567  {
10568  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(4, Ptr->DepartureTime, RepNum, IncMins)) + ": Depart from " + Ptr->LocationName;
10569  }
10570  else if(Ptr->FormatType == PassTime) // new
10571  {
10572  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(5, Ptr->EventTime, RepNum, IncMins)) + ": Pass " + Ptr->LocationName;
10573  }
10574  else if(Ptr->Command == "Fns")
10575  {
10576  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(6, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10577  TrainController->GetRepeatHeadCode(46, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10578  PartStr = ControllerGetNewServiceDepartureInfo(11, Ptr, RepNum, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to PartStr
10579  }
10580  else if(Ptr->Command == "F-nshs")
10581  {
10582  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(7, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10583  Ptr->NonRepeatingShuttleLinkHeadCode + " at " + Ptr->LocationName;
10584  PartStr = ControllerGetNewServiceDepartureInfo(13, Ptr, 0, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10585  //note that use LinkedTrainEntryPtr and not NonRepeatingShuttleLinkEntryPtr because the forward link from the feeder is LinkedTrainEntryPtr.
10586  //NonRepeatingShuttleLinkEntryPtr is in the shuttle's ActionVector to point back to the feeder.
10587  //NonRepeatingShuttleLinkEntryPtr is used below from the last shuttle as the forward link to the finishing service
10588  }
10589 //Since this is a new continuation entry service it can't be Fns-sh or Frh-sh but leave these in for consistency with TTrain::FloatingTimetableString
10590  else if((Ptr->Command == "Fns-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
10591  {
10592  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(8, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10593  TrainController->GetRepeatHeadCode(47, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
10594  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
10595  PartStr = ControllerGetNewServiceDepartureInfo(15, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10596  }
10597  else if((Ptr->Command == "Fns-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
10598  {
10599  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(9, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10600  Ptr->NonRepeatingShuttleLinkHeadCode, +" at " + Ptr->LocationName;
10601  PartStr = ControllerGetNewServiceDepartureInfo(17, Ptr, 0, TTDEPtr, Ptr->NonRepeatingShuttleLinkEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10602  }
10603  else if((Ptr->Command == "Frh-sh") && (RepNum < (TTDEPtr->NumberOfTrains - 1))) // not the last repeat number
10604  {
10605  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(10, Ptr->EventTime, RepNum, IncMins)) + ": Form new service " +
10606  TrainController->GetRepeatHeadCode(48, Ptr->OtherHeadCode, RepNum + 1, IncDig) + " at " + Ptr->LocationName;
10607  // use RepNum+1 because it's the repeat number of the NEXT shuttle service that is relevant
10608  PartStr = ControllerGetNewServiceDepartureInfo(19, Ptr, RepNum + 1, TTDEPtr, Ptr->LinkedTrainEntryPtr, IncMins, IncDig, PartStr); //if there is a next service this adds the new service departure time to RetStr
10609  }
10610  else if((Ptr->Command == "Frh-sh") && (RepNum >= (TTDEPtr->NumberOfTrains - 1))) // last repeat number
10611  {
10612  PartStr = "Terminate at " + Ptr->LocationName;
10613  }
10614  else if(Ptr->Command == "Frh")
10615  {
10616  PartStr = "Terminate at " + Ptr->LocationName;
10617  }
10618  else if(Ptr->Command == "Fer")
10619  {
10620  AnsiString AllowedExits;
10621  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(11, Ptr->EventTime, RepNum, IncMins)) + ": Exit railway" +
10622  TrainController->GetExitLocationAndAt(3, Ptr->ExitList, AllowedExits) + AllowedExits;
10623  }
10624  else if(Ptr->Command == "Fjo")
10625  {
10626  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(12, Ptr->EventTime, RepNum, IncMins)) + ": Join " + TrainController->GetRepeatHeadCode(49,
10627  Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10628  }
10629  else if(Ptr->Command == "jbo")
10630  {
10631  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(13, Ptr->EventTime, RepNum, IncMins)) + ": Joined by " + TrainController->GetRepeatHeadCode
10632  (50, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10633  }
10634  else if(Ptr->Command == "fsp")
10635  {
10636  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(14, Ptr->EventTime, RepNum, IncMins)) + ": Front split to " +
10637  TrainController->GetRepeatHeadCode(51, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10638  }
10639  else if(Ptr->Command == "rsp")
10640  {
10641  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(15, Ptr->EventTime, RepNum, IncMins)) + ": Rear split to " +
10642  TrainController->GetRepeatHeadCode(52, Ptr->OtherHeadCode, RepNum, IncDig) + " at " + Ptr->LocationName;
10643  }
10644  else if(Ptr->Command == "cdt")
10645  {
10646  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(16, Ptr->EventTime, RepNum, IncMins)) + ": Change direction at " + Ptr->LocationName;
10647  }
10648  else if(Ptr->Command == "dsc")
10649  {
10650  PartStr = Utilities->Format96HHMM(GetControllerTrainTime(27, Ptr->EventTime, RepNum, IncMins)) + ": Change description at " + Ptr->LocationName;
10651  }
10652  if(RetStr != "")
10653  {
10654  RetStr = RetStr + '\n' + PartStr;
10655  }
10656  else
10657  {
10658  RetStr = PartStr;
10659  }
10660  Count++;
10661  }
10662  while(Ptr < TTDEPtr->ActionVector.end() && (Count < 33) && ((Ptr->Command == "") || ((Ptr->Command != "") && (Ptr->Command[1] != 'F'))));
10663  // limit of 33 allows a max of 34 entries (may have gone from 32 to 34 because of a TimeTimeLoc), which with track and train status gives
10664  // a max of 48 lines, at 13 pixels each, = 624 pixels & screen height has 641 so will fit comfortably. Also 34 timetable entries is as far
10665  // forward as anyone should wish to see without looking at the full timetable
10666  Utilities->CallLogPop(2072);
10667  return(RetStr);
10668 }
10669 
10670 // ---------------------------------------------------------------------------
10671 
10673  TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
10674 { //no delays as train not entered yet //above added IncrementalDigits at v2.18.0 for GetRepeatHeadCode
10675  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString(Ptr - TDEPtr->ActionVector.begin()) + ","
10676  + AnsiString(RptNum) + ",ControllerGetNewServiceDepartureInfo," + TDEPtr->HeadCode);
10677  AnsiString DepTime = "", EventTime = "";
10678  bool CDTFlag = false; //reports if train changes direction before departs
10679  TActionVector NewServiceAV = LinkedTrainDataPtr->ActionVector;
10680  AnsiString CurrentLocation = NewServiceAV.at(0).LocationName; //added at v2.12.0 to show departure direction
10681  AnsiString TowardsLocation = ""; //added at v2.12.0 to show departure direction
10682  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++) //added at v2.12.0 to obtain departure direction
10683  {
10684  if((AVI->LocationName != CurrentLocation) && (AVI->LocationName != "") && (TowardsLocation == ""))
10685  {
10686  TowardsLocation = AVI->LocationName;
10687  }
10688  else if((AVI->Command == "Fer") && (TowardsLocation == "") && !AVI->ExitList.empty())
10689  {
10690  TTrackElement TE = Track->TrackElementAt(1453, (AVI->ExitList.front()));
10691  if(TE.ActiveTrackElementName != "")
10692  {
10693  TowardsLocation = TE.ActiveTrackElementName;
10694  }
10695  else
10696  {
10697  TowardsLocation = AnsiString("track element ID ") + TE.ElementID;
10698  }
10699  }
10700  }
10701  for(TActionVectorIterator AVI = NewServiceAV.begin(); AVI < NewServiceAV.end(); AVI++)
10702  {
10703  if(AVI->Command == "cdt")
10704  {
10705  CDTFlag = !CDTFlag; //toggles flag - allows for there being more than one cdt before departure
10706  continue;
10707  }
10708  if((AVI->Command == "fsp") || (AVI->Command == "rsp"))
10709  {
10710  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(21, AVI->EventTime, RptNum, IncrementalMinutes));
10711  RetStr += "\nNew service splits at " + EventTime;
10712  Utilities->CallLogPop(2237);
10713  return(RetStr);
10714  }
10715  if(AVI->Command == "jbo")
10716  {
10717  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(30, AVI->EventTime, RptNum, IncrementalMinutes));
10718  RetStr += "\nNew service joined by " + GetRepeatHeadCode(70, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
10719  Utilities->CallLogPop(2238); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
10720  return(RetStr);
10721  }
10722  if((AVI->Command == "Fns") || (AVI->Command == "F-nshs") || (AVI->Command == "Fns-sh"))
10723  {
10724  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(31, AVI->EventTime, RptNum, IncrementalMinutes));
10725  RetStr += "\nNew service finishes and forms another new service at " + EventTime;
10726  Utilities->CallLogPop(2607);
10727  return(RetStr);
10728  }
10729  if(AVI->Command == "Fjo")
10730  {
10731  EventTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(22, AVI->EventTime, RptNum, IncrementalMinutes));
10732  RetStr += "\nNew service finishes and joins " + TrainController->GetRepeatHeadCode(71, AVI->OtherHeadCode, RptNum, IncrementalDigits) + " at " + EventTime;
10733  Utilities->CallLogPop(2608); //above added GetRepeatHeadCode at v2.18.0 (was just AVI->OtherHeadCode before)
10734  return(RetStr);
10735  }
10736  if(AVI->Command == "Frh")
10737  {
10738  RetStr += "\nNew service finishes and remains at location.";
10739  Utilities->CallLogPop(2609);
10740  return(RetStr);
10741  }
10742  if((AVI->FormatType == TimeLoc) && (AVI->DepartureTime > TDateTime(-1))) //departure time set
10743  {
10744  DepTime = Utilities->Format96HHMM(TrainController->GetControllerTrainTime(23, AVI->DepartureTime, RptNum, IncrementalMinutes));
10745  if(CDTFlag)
10746  {
10747  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
10748  {
10749  RetStr += "\nNew service changes direction then departs towards " + TowardsLocation + " at " + DepTime;
10750  }
10751  else
10752  {
10753  RetStr += "\nNew service changes direction then departs at " + DepTime;
10754  }
10755  }
10756  else
10757  {
10758  if(TowardsLocation != "") //added at v2.12.0 to show departure direction
10759  {
10760  RetStr += "\nNew service departs towards " + TowardsLocation + " at " + DepTime;
10761  }
10762  else
10763  {
10764  RetStr += "\nNew service departs at " + DepTime;
10765  }
10766  }
10767  Utilities->CallLogPop(2239);
10768  return(RetStr);
10769  }
10770  }
10771  Utilities->CallLogPop(2223);
10772  return(RetStr);
10773 }
10774 
10775 // ---------------------------------------------------------------------------
10776 // $$$$$$$$$$$$$$$$$$$$$$ Start of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$$
10777 /*
10778  Note: The terms 'action' and 'entry' have been used freely for individual code lines within services in comments & in variable names, but
10779  for messages and in the manual and help files the term Entry is reserved for a complete service or train (i.e. an entry in the timetable),
10780  and 'event' is reserved for and individual code line within a service. Repeats use the term 'item' if they use any at all.
10781 
10782  In references to 'HeadCode' can have an optional prefix - up to 4 additional characters that can be anything, so long as last 4 digits
10783  represent the headcode. This allows links to be uniquely identified regardless of the headcode - so can have same headcodes as often as
10784  user wishes
10785 
10786  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
10787  descriptive text or anything user wishes
10788  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
10789  be ignored) is taken as the timetable start time.
10790  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
10791  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
10792  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
10793  within the timetable if required.
10794  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
10795  services)
10796  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
10797  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
10798 
10799  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
10800  text timetable file easier
10801 
10802  form:-
10803  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
10804  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
10805  then multiple entries, separated by commas, of the form:-
10806 
10807  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
10808  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
10809  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
10810 
10811  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
10812  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
10813  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
10814 
10815  HH:MM;Command (cdt) }TimeCmd }
10816  HH:MM;Command;Description (dsc) }TimeCmdDescription }
10817  HH:MM;Location (arr & dep) }TimeLoc }
10818  HH:MM;HH:MM;Location }TimeTimeLoc }
10819  HH:MM;pas;Location }PassTime }
10820  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
10821  HH:MM;Fer;set of allowable IDs }ExitRailway }
10822  Command (Frh only) }FinRemHere }
10823 
10824  R;mm;dd;nn. Repeat Repeat entry
10825 
10826  Formats:
10827 
10828  Command only: Frh
10829  Time;Command: cdt
10830  Time;Command;Description dsc
10831  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
10832  Time;Command;2 Element IDs: Snt
10833  Time;Comand;n Element IDs: Fer
10834  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
10835  Time;Command;2 Element IDs;Headcode Snt-sh
10836  Time;Command;Location pas
10837  Time;Location Arr Dep
10838  Time;Time;Location Arr & dep together
10839 
10840  10 Non-linked entries: Snt (located or unlocated); pas; cdt; dsc; TimeLoc arr & dep; TimeTimeLoc; Fer; Frh
10841 
10842  9 1x Linked entries: Non-shuttle: fsp or rsp -> Sfs; Fns -> Sns; Fjo -> jbo; times must match, headcodes must match
10843  Shuttle: F-nshs -> Sns-sh: times match, F-nshs HeadCode matches Sns-sh 2nd Headcode;
10844  Fns-sh -> Sns-fsh: Fns-sh time + all repeats = Sns-fsh time, Fns-sh 2nd headcode matches Sns-fsh Headcode
10845 
10846  4 2x Linked entries, all shuttles:
10847 
10848  Frh-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Frh-sh Headcode = Snt-sh Headcode;
10849  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Frh-sh Headcode = Sns-sh 1st Headcode;
10850  -> Remain Here (at finish location after all repeats)
10851  Fns-sh -> Snt-sh: Frh-sh time = Snt-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Snt-sh Headcode
10852  -> Sns-sh: Frh-sh time = Sns-sh time + 1 repeat while repeating, Fns-sh 1st Headcode = Sns-sh 1st Headcode
10853 
10854  Moving/AtLoc states:-
10855 
10856  Successor state Type
10857 
10858  Snt located AtLoc ) Snt AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
10859  Snt Unlocated Moving ) Snt Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
10860  Sfs AtLoc )
10861  Sns AtLoc ) Start
10862  Sns-fsh AtLoc )
10863  Snt-sh AtLoc )
10864  Sns-sh AtLoc )
10865 
10866  pas Moving )
10867  jbo AtLoc )
10868  fsp AtLoc )
10869  rsp AtLoc ) Intermediate
10870  cdt AtLoc )
10871  dsc AtLoc )
10872  TimeLoc arr Moving (bef), AtLoc (aft) )
10873  TimeLoc dep AtLoc (bef), Moving (aft) )
10874  TimeTimeLoc Moving )
10875 
10876  Fns Repeat/Nothing)
10877  Fjo Repeat/Nothing)
10878  Frh Repeat/Nothing)
10879  Fer Repeat/Nothing) Finish
10880  Frh-sh Repeat )
10881  Fns-sh Repeat )
10882  F-nshs Nothing )
10883 
10884  Descriptions:
10885  Snt New train
10886  Sfs New service from split
10887  Sns New service from another service
10888  Sns-fsh New non-repeating service from a shuttle service
10889  Snt-sh New shuttle train at a timetabled stop
10890  Sns-sh New shuttle service from a feeder service
10891 
10892  pas Pass
10893  jbo Be joined by another train
10894  fsp Front split
10895  rsp Rear split
10896  cdt Change direction of train
10897  dsc Change description of train
10898  TimeLoc arr Arrival
10899  TimeLoc dep Departure
10900  TimeTimeLoc Arrival and departure
10901 
10902  Fns Finish & form a new service
10903  Fjo Finish & join another train
10904  Frh Finish & remain here
10905  Fer Finish & exit railway
10906  Frh-sh Finish & repeat shuttle, finally remain here
10907  Fns-sh Finish & repeat shuttle, finally form a non-repeating service
10908  F-nshs Finish & form a shuttle feeder service
10909 */
10910 
10911 bool TTrainController::TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway) // true for success
10912 {
10913  // Error messages mainly given in called functions, five are given here - empty file; inability to find a start time; timetable containing
10914  // a line that is too long; timetable containing too few lines; and timetable failed to open.
10915  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TimetableIntegrityCheck," + AnsiString(FileName));
10916  // new for v0.2b
10917  // compile ActiveTrackElementNameMap
10918  TTrack::TActiveTrackElementNameMapEntry ActiveTrackElementNameMapEntry;
10919 
10921  for(unsigned int x = 0; x < Track->TrackVector.size(); x++)
10922  {
10923  // if((Track->TrackElementAt(, x).ActiveTrackElementName != "") && (Track->TrackElementAt(, x).TrackType != Continuation))
10925  == Track->ContinuationNameMap.end())
10926  {
10927  // exclude any name that appears in a continuation, error message given in tt validation if try to include such a name in a tt
10928  ActiveTrackElementNameMapEntry.first = Track->TrackElementAt(1035, x).ActiveTrackElementName;
10929  ActiveTrackElementNameMapEntry.second = 0; // this is a dummy value
10930  Track->ActiveTrackElementNameMap.insert(ActiveTrackElementNameMapEntry);
10931  }
10932  }
10934  // end of new section
10935  std::ifstream TTBLFile(FileName, std::ios_base::binary);
10936 
10937  // binary mode so the "\r\n" pairs stay as they are rather than being entered as '\n'
10938  if(TTBLFile.is_open())
10939  {
10940  char *TrainTimetableString = new char[10000];
10941  // enough for over 200 stations, should be adequate!
10942  bool EndOfFile = false;
10943  int Count = 0;
10944  // counts 'relevant' lines, i.e ignores any before the start time on its own line
10945  TTBLFile.getline(TrainTimetableString, 10000, '\0');
10946  // delimiter is '\0' as it's an AnsiString
10947  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
10948  // file empty - stores a null in 1st position if doesn't load any characters
10949  {
10950  // may still have eof even if read a line (no CRLF at end), and
10951  // if so need to process it
10952  TimetableMessage(GiveMessages, "Timetable invalid - file empty");
10953  TTBLFile.close();
10954  delete[] TrainTimetableString;
10955  Utilities->CallLogPop(1611);
10956  return(false);
10957  }
10958  AnsiString OneLine(TrainTimetableString);
10959  bool FinalCallFalse = false;
10960  while((Count == 0) && !ProcessOneTimetableLine(5, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
10961  // get rid of lines before the start time
10962  {
10963  // ProcessOneTimetableLine returns true for a valid start time, an EndOfFile &/or a blank entry
10964  TTBLFile.getline(TrainTimetableString, 10000, '\0');
10965  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
10966  // stores a null in 1st position if doesn't load any characters
10967  {
10968  // may still have eof even if read a line (no CRLF at end), and
10969  // if so need to process it
10970  TimetableMessage(GiveMessages, "Timetable invalid - unable to find a valid start time on its own line");
10971  TTBLFile.close();
10972  delete[] TrainTimetableString;
10973  Utilities->CallLogPop(772);
10974  return(false);
10975  }
10976  OneLine = AnsiString(TrainTimetableString);
10977  }
10978  // here when have accepted the start time
10979  Count++; // increment past the start time
10980  while(!EndOfFile)
10981  {
10982  TTBLFile.getline(TrainTimetableString, 10000, '\0');
10983  // get next line after start time
10984  if(TTBLFile.eof() && (TrainTimetableString[0] == '\0'))
10985  // stores a null in 1st position if doesn't load any characters
10986  {
10987  // may still have eof even if read a line (no CRLF at end), and
10988  // if so need to process it
10989  EndOfFile = true;
10990  OneLine = "";
10991  }
10992  else
10993  {
10994  OneLine = AnsiString(TrainTimetableString);
10995  }
10996  if(OneLine.Length() > 9999)
10997  {
10998  TimetableMessage(GiveMessages, "Timetable contains a line that is too long - 10,000 or more characters!");
10999  TTBLFile.close();
11000  delete[] TrainTimetableString;
11001  Utilities->CallLogPop(789);
11002  return(false);
11003  }
11004  bool FinalCallFalse = false;
11005  if(!ProcessOneTimetableLine(6, Count, OneLine, EndOfFile, FinalCallFalse, GiveMessages, CheckLocationsExistInRailway))
11006  // false for FinalCall - just checking at this stage
11007  {
11008  TTBLFile.close();
11009  delete[] TrainTimetableString;
11010  Utilities->CallLogPop(770);
11011  return(false);
11012  }
11013  if(EndOfFile && (Count < 2))
11014  // Timetable must contain at least two relevant lines, one for start time and at least one train
11015  {
11016  TimetableMessage(GiveMessages, "Timetable has too few or no relevant entries - must have a start time on its own line and at least one train");
11017  TTBLFile.close();
11018  delete[] TrainTimetableString;
11019  Utilities->CallLogPop(771);
11020  return(false);
11021  }
11022  Count++;
11023  }
11024  delete[] TrainTimetableString;
11025  TTBLFile.close();
11026  } // if(TTBLFile.is_open())
11027  else
11028  {
11029  TimetableMessage(GiveMessages, "Failed to open timetable file, make sure it's spelled correctly, it exists and isn't open in another application");
11030  Utilities->CallLogPop(2154);
11031  return(false);
11032  }
11033  Utilities->CallLogPop(753);
11034  return(true);
11035 }
11036 
11037 // ---------------------------------------------------------------------------
11038 
11039 bool TTrainController::ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages,
11040  bool CheckLocationsExistInRailway) // return true for success
11041 
11042 /* Format:
11043  Prior to start time, anything except a line beginning with a time [...leading spaces...] HH:MM is ignored - can be
11044  descriptive text or anything user wishes
11045  A time on its own line [HH:MM], with or without leading spaces, but with anything following it before the CR (which will
11046  be ignored) is taken as the timetable start time.
11047  Thereafter there must be text on every line in the timetable, as the first blank line (or end of file) will be taken as the end of the
11048  timetable. Text can follow the 'end of timetable' blank line if the user wishes.
11049  A line within the timetable beginning with '*', with or without leading spaces, is ignored. Such lines can add text
11050  within the timetable if required.
11051  Timetable entries consist of one line per headcode (i.e. per service, not necessarily per train, as one train can run several different
11052  services)
11053  Each line starts with HeadCode & full train information for a new train (Snt or Snt-sh), or, for a continuing service
11054  (Sfs, Sns, Sns-sh or Sns-fsh), can have (a) Headcode only or (b) HeadCode + Description, nothing else
11055 
11056  All leading & trailing spaces before & after a line or any entry in a line are stripped off - these can be included to make reading a
11057  text timetable file easier
11058 
11059  form:-
11060  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
11061  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
11062  then multiple entries, separated by commas, of the form:-
11063 
11064  Format FormatType
11065  [W]HH:MM;Command (cdt) }TimeCmd }
11066  [W]HH:MM;dsc;new description }TimeCmdDescription }
11067  [W]HH:MM;Fer;set of allowable IDs }ExitRailway }
11068  [W]HH:MM;pas;Location }PassTime }
11069  [W]HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
11070  [W]HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode }
11071  [W]HH:MM;F-nshs;non-repeating headcode }FNSNonRepeatToShuttle }
11072  [W]HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle } Train action entries
11073  [W]HH:MM;Snt-sh;RearStartIdent FrontStartIdent;FSH HeadCode }SNTShuttle }
11074  [W]HH:MM;Sns-sh;FSH HeadCode;F-nshs HeadCode (non-repeating) }SNSShuttle }
11075  [W]HH:MM;Fns-sh;Details }FSHNewService }
11076  [W]HH:MM;Location (arr & dep) }TimeLoc }
11077  [W]HH:MM;HH:MM;Location }TimeTimeLoc }
11078  Command (Frh only) }FinRemHere }
11079 
11080  R;mm;dd;nn. Repeat Repeat entry
11081 
11082  Two times represent arrival & departure, without any other events between (if arrival and departure times are the same
11083  then departure is 30 sec after arrival), single time represents (a) event time; (b) arrival time if train not already
11084  at location; or (c) departure time if train already at location (including train started at location either as a new
11085  train or as a continuation service train at that location). All lines must contain a start entry and a finish entry,
11086  the finish being the last unless there is a repeat entry. The repeat entry begins with 'R', then the incremental
11087  minutes, incremental train headcode last 2 digits, and number of repeats.
11088 
11089  Shuttle entries are where can loop back to an earlier Snt-sh or Sns-sh entry from a Frh-sh or Fns-sh (Finish Shuttle)
11090  entry. Here the shuttle start can have two entries, one from a set position (Snt-sh, must be located) or from a F-nshs
11091  (Sns-sh) - with NO repeat from this source, and from a Fxx-sh, with repeats. After all shuttle repeats Frh-sh remains
11092  where it is, and Fns-sh links to a new service (via an Sns entry), but there must be no repeats in this new service
11093  (it's for a shuttle train to return to depot at end of services)
11094 
11095  Command/Location & details are as follows:-
11096 
11097  Although headcodes can be duplicated, all joins, splits, new services etc give other headcode from both trains' povs, and
11098  these have to match once only, i.e. if 2E44 splits to 2E45 then it can't split to 2E45 anywhere else, and 2E45 must give
11099  2E44 in its Sfs entry. All these are checked.
11100  ***add note re shuttles & their use of otherheadcodes + non-repeating headcodes***
11101 
11102  Start commands:-
11103  Snt (StartNew) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't confuse
11104  with loc as a start entry can't have a location as details)
11105  Sfs (TimeCmdHeadCode) = Start From Split, create a new train that has split from another train (& listed in other train's
11106  timetable line), details = other headcode - (can't confuse with loc as start can't be a loc)
11107  Sns (TimeCmdHeadCode) = Start, headcode change from earlier service - no need to create train as already exists, it just
11108  changes its relevant information, details = old headcode (can't confuse with loc as start can't be a loc)
11109  Snt-sh (SNTShuttle) = Start New Train, i.e. create new train, details = rearstartident, space, frontstartident (can't
11110  confuse with loc as start can't be a loc) then the Fsh-XX service headcode (OtherHeadCode can't be same headcode)
11111  Sns-sh (SNSShuttle) = Start, headcode change from earlier service - no need to create train as already exists, it just
11112  changes its relevant information, details = the FSH-XX service headcode (OtherHeadCode, can't be same headcode)
11113  followed by the non-repeating F-nshs headcode (NonRepeatingShuttleLinkHeadCode)
11114  Sns-fsh (SNSNonRepeatFromShuttle) = Start as a non-repeating service from a shuttle service that has finished all its
11115  repeats, details = NonRepeatingShuttleLinkHeadCode for the corresponding shuttle Fns-sh service
11116 
11117  Intermediate commands:-
11118  Time - Location (TimeLoc), can be arrival or departure depending on context
11119  Time Time location (TimeTimeLoc), arrival and departure
11120  Location Name (exactly as used in the railway) in TimeLoc & TimeTimeLoc means that the train is required to stop at the location
11121  pas (PassTime), Time;pas;Location
11122  jbo (TimeCmdHeadCode) = Joined By Other = joined by other train, details = new headcode (await other train - may be delayed). Note that the
11123  joining train's finish details must correspond or the file check will fail
11124  fsp (TimeCmdHeadCode) = Front Split = a new train splits away from front of this train, both trains in same direction, details = new headcode (create
11125  new train - that train's starting information must correspond)
11126  rsp (TimeCmdHeadCode) = Rear Split = a new train splits away from rear of this train, both trains in same direction, details = new headcode (create
11127  new train - that train's starting information must correspond)
11128  cdt (TimeCmd) = Change Direction of Train = change direction, no details needed & no train creation
11129  dsc (TimeCmdDescription) = Change description of train = new description only, no train creation
11130 
11131  Finish commands:-
11132  Fns (TimeCmdHeadCode) = Finish New Service = finish, form new service in same direction, details = new headcode (no train
11133  creation)
11134  F-nshs (FNSShuttle) = Finish New Service (Shuttle) = finish, form new shuttle service in same direction, details =
11135  shuttle headcode (no train creation)
11136  Fjo (TimeCmdHeadCode) = Finish Join Other = finish, join other train (which must be on an adjacent element, either end -
11137  may have to wait for it), details = new headcode (delete train)
11138  Frh (FinRemHere) = Finish Remain Here = stay here indefinitely, no details & no time needed
11139  Fer (ExitRailway) = Finish, exit railway (i.e at a continuation) - details = set of allowable exit IDs
11140  Frh-sh (TimeCmdHeadCode) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done remain
11141  here
11142  Fns-sh (FSHNewService) = Finish then restart as a shuttle using Snt-sh or Sns-sh, when all shuttle repeats done form new
11143  service via Sns-fsh using the NonRepeatingShuttleLinkHeadCode
11144 
11145  Repeat:-
11146  R;mm;dd;nn (Repeat) where mm = minute increment, dd = 2nd 2 headcode digit increment & nn = no. of repeats (no max as can duplicate
11147  headcodes - it is up to user to avoid duplicates if he/she wishes to.
11148 
11149  Checks carried out with error messages in this function:-
11150  At least one comma in a service line (it's based on a .csv file)
11151  No entries following train information;
11152  At least one comma in remainder after train information (i.e at least a start and a finish entry);
11153  SplitEntry returns false in an intermediate entry - message repeats the entry for information;
11154  First entry not a start entry;
11155  Train information incomplete before a start entry;
11156  Entry follows a finish entry but doesn't begin with 'R';
11157  SplitEntry returns false in a finish entry - message repeats the entry for information;
11158  Last action entry isn't a finish entry.
11159 
11160  Function returns false with no message if:-
11161  Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
11162  time is found at all then an error message is given in the calling function);
11163  SplitTrainInfo returns false (message given in called function);
11164  SplitRepeat returns false (message given in called function).
11165 */
11166 {
11167  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ProcessOneTimetableLine," + AnsiString(Count) + "," + OneLine + "," +
11168  AnsiString((short)FinalCall) + "," + AnsiString((short)CheckLocationsExistInRailway));
11169  TTrainDataEntry TempTrainDataEntry;
11170 
11171  EndOfFile = false;
11172  StripSpaces(0, OneLine);
11173  // strip both leading and trailing spaces at ends of line and spaces before and after all commas and
11174  // semicolons within the line
11175  ServiceReference = "";
11176  if(OneLine != "")
11177  {
11178  if(OneLine[1] != '*')
11179  {
11180  int SCPos = OneLine.Pos(';');
11181  if(SCPos == 0)
11182  {
11183  ServiceReference = OneLine.SubString(1, 8);
11184  }
11185  else
11186  {
11187  ServiceReference = OneLine.SubString(1, (SCPos - 1));
11188  }
11189  }
11190  }
11191  bool AllCommas = true;
11192 
11193  for(int x = 1; x < OneLine.Length() + 1; x++) // check for nothing but commas (may be all commas if created from Excel) or a blank line
11194  {
11195  if(OneLine[x] != ',')
11196  {
11197  AllCommas = false;
11198  }
11199  }
11200  if(AllCommas || (OneLine == ""))
11201  {
11202  if(Count > 0)
11203  {
11204  EndOfFile = true;
11205  // returns true for a blank line (or a line of all commas) - treated as end of file
11206  Utilities->CallLogPop(1018);
11207  return(true);
11208  }
11209  else // count == 0 so not yet found a start time, no message to be given
11210  {
11211  Utilities->CallLogPop(754);
11212  return(false);
11213  }
11214  }
11215  AnsiString First = "", Second = "", Third = "", Fourth = "";
11216  int RearStartOrRepeatMins = 0, FrontStartOrRepeatDigits = 0, NumberOfRepeats = 0;
11217  TDateTime EventTime(0), ArrivalTime(0), DepartureTime(0);
11218  TDateTime StartTime(0);
11219  TNumList ExitList;
11220  bool Warning = false;
11221 
11222  if(Count == 0) // no start time found yet
11223  {
11224 /* dropped at v0.6b
11225  AnyHeadCodeValid = false;
11226  if(OneLine.SubString(6,5) == ";0000")
11227  {
11228  AnyHeadCodeValid = true;
11229  }
11230 */
11231  if(!CheckTimeValidity(0, OneLine, StartTime))
11232  {
11233  // no message is given for an invalid time as it's assumed to be an irrelevant line
11234  // if no start time is found at all then an error message is given in the calling function
11235  // AnyHeadCodeValid = false;
11236  Utilities->CallLogPop(755);
11237  return(false);
11238  }
11239  if(FinalCall) // here if start time valid
11240  {
11241  TTClockTime = StartTime;
11242  TimetableStartTime = StartTime;
11243  }
11244  }
11245  else
11246  {
11247  AnsiString TrainInfoStr = "", HeadCode = "", Description = "";
11248  int StartSpeed = 0, MaxRunningSpeed = 0, Mass = 0;
11249  double MaxBrakeRate = 0;
11250  double PowerAtRail = 0;
11251  int SignallerSpeed = 0;
11252  if(OneLine[1] == '*')
11253  {
11254  Utilities->CallLogPop(1581);
11255  return(true);
11256  // ignore any line beginning with '*' but return true as there is no error
11257  }
11258  int Pos = OneLine.Pos(',');
11259  if(Pos == 0)
11260  {
11261  int SubStringLength = 20;
11262  if(OneLine.Length() < 20)
11263  {
11264  SubStringLength = OneLine.Length();
11265  }
11266  TimetableMessage(GiveMessages, "Error in timetable - entry incomplete: see '" + OneLine.SubString(1, SubStringLength) + "'....");
11267  Utilities->CallLogPop(766);
11268  return(false);
11269  }
11270  TrainInfoStr = OneLine.SubString(1, Pos - 1);
11271  if(!SplitTrainInfo(0, TrainInfoStr, HeadCode, Description, StartSpeed, MaxRunningSpeed, Mass, MaxBrakeRate, PowerAtRail, SignallerSpeed,
11272  GiveMessages)) // error messages given in SplitTrainInfo
11273  {
11274  Utilities->CallLogPop(773);
11275  return(false);
11276  }
11277  if(FinalCall)
11278  {
11279  // store Train info - conversions done in SplitTrainInfo
11280  // only headcode mandatory for continuing services
11281  //HeadCode = ServiceReference until final section of SecondPassActions
11282  TempTrainDataEntry.HeadCode = HeadCode;
11283  TempTrainDataEntry.ServiceReference = HeadCode;
11284  TempTrainDataEntry.FixedDescription = Description; //name changed at v2.16.1
11285  TempTrainDataEntry.ExplicitDescription = false;
11286  if(Description != "")
11287  {
11288  TempTrainDataEntry.ExplicitDescription = true;
11289  }
11290  TempTrainDataEntry.StartSpeed = StartSpeed;
11291  TempTrainDataEntry.Mass = Mass;
11292  TempTrainDataEntry.MaxRunningSpeed = MaxRunningSpeed;
11293  TempTrainDataEntry.MaxBrakeRate = MaxBrakeRate;
11294  TempTrainDataEntry.PowerAtRail = PowerAtRail;
11295  TempTrainDataEntry.SignallerSpeed = SignallerSpeed;
11296  TTrainOperatingData TempTrainOperatingData;
11297  TempTrainDataEntry.TrainOperatingDataVector.push_back(TempTrainOperatingData); // push empty vector for now
11298  }
11299  AnsiString NewRemainder = OneLine.SubString(Pos + 1, OneLine.Length() - Pos);
11300  // now left with series of entries for this train, but there may be a string of commas at the end of the line if created by Excel
11301  // so strip them off
11302  while(NewRemainder[NewRemainder.Length()] == ',')
11303  {
11304  if(NewRemainder.Length() > 1)
11305  {
11306  NewRemainder = NewRemainder.SubString(1, NewRemainder.Length() - 1);
11307  }
11308  else
11309  {
11310  NewRemainder = "";
11311  break;
11312  }
11313  }
11314  // check if zero length & fail if so
11315  if(NewRemainder == "")
11316  {
11317  TimetableMessage(GiveMessages, "Error in timetable - no events following train: '" + OneLine + "'");
11318  Utilities->CallLogPop(769);
11319  return(false);
11320  }
11321  // now have one more entry than there are commas
11322  int CommaCount = 0;
11323  for(int x = 1; x < NewRemainder.Length() + 1; x++)
11324  {
11325  if(NewRemainder[x] == ',')
11326  {
11327  CommaCount++;
11328  }
11329  } // must have at least 1 comma, for start & finish entries, unless train is entered under signaller control
11330  if(CommaCount == 0)
11331  {
11332  if((NewRemainder.SubString(7, 3) != "Snt") || (NewRemainder[NewRemainder.Length()] != 'S'))
11333  {
11334  int SubStringLength = 20;
11335  if(OneLine.Length() < 20)
11336  {
11337  SubStringLength = OneLine.Length();
11338  }
11339  TimetableMessage(GiveMessages,
11340  "Error in timetable - must have at least a start and a finish event for a train that is not started under signaller control - see line beginning: '" +
11341  OneLine.SubString(1, SubStringLength) + "'....");
11342  Utilities->CallLogPop(783);
11343  return(false);
11344  }
11345  }
11346  AnsiString OneEntry = "";
11347  TTimetableFormatType FormatType;
11348  TTimetableSequenceType SequenceType;
11349  TTimetableLocationType LocationType;
11350  TTimetableShuttleLinkType ShuttleLinkType;
11351  bool FinishFlag = false;
11352 // bool NewTrain = false;//added at v2.14.0 to record created trains for later zero power checks <-- not needed after zero power restriction dropped
11353  for(int x = 0; x < CommaCount + 1; x++)
11354  {
11355  if((CommaCount == 0) || (x < CommaCount))
11356  // i.e. train entered under signaller control with no repeats, or entry is not the last,
11357  // in which case there's a comma & finish element or repeat still to come this entry could
11358  // be a finish but can't be a repeat
11359  {
11360  if(CommaCount == 0)
11361  {
11362  OneEntry = NewRemainder;
11363  NewRemainder = "";
11364  }
11365  else
11366  {
11367  Pos = NewRemainder.Pos(',');
11368  OneEntry = NewRemainder.SubString(1, Pos - 1);
11369  NewRemainder = NewRemainder.SubString(Pos + 1, NewRemainder.Length() - Pos);
11370  }
11371  First = "";
11372  Second = "";
11373  Third = "";
11374  Fourth = "";
11375  RearStartOrRepeatMins = 0;
11376  FrontStartOrRepeatDigits = 0;
11377  NumberOfRepeats = 0;
11378  if(!SplitEntry(0, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
11379  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
11380  {
11381  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'");
11382  Utilities->CallLogPop(756);
11383  return(false);
11384  }
11385 /* not needed at v2.19.0
11386  if((Second == "Snt") || (Second == "Snt-sh")) //added at v2.14.0, see above
11387  {
11388  NewTrain = true; not needed when zero power restrictions removed
11389  }
11390 */
11391  // check if warning for Frh or Fjo & reject
11392  if(Warning && (Second == "Frh"))
11393  {
11394  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry + "': warnings cannot be given for 'Frh' events");
11395  Utilities->CallLogPop(1793);
11396  return(false);
11397  }
11398  if(Warning && (Second == "Fjo"))
11399  {
11400  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11401  "': warnings cannot be given for 'Fjo' events, for a train join warning add a 'W' prefix to the 'jbo' event");
11402  Utilities->CallLogPop(1794);
11403  return(false);
11404  }
11405  //below added at v2.14.0 to prevent unpowered trains attempting to be joined by (Second == jbo), split (Second -- fsp or rsp),
11406  //or change direction. Form a new service dealt with below for zero power as it's a finish event. <-- removed at v2.19.0
11407 
11408 /* restriction removed at v2.19.0
11409  if(NewTrain && (PowerAtRail < 1) && (Second == "jbo"))
11410  {
11411  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11412  "': a train created without power can't 'be joined by' another train (i.e. can't include command 'jbo'), "
11413  "use command 'Fjo' (i.e. 'join' another train) instead immediately after the line containing 'Snt', and use "
11414  "command 'jbo' for the train it is to join.");
11415  Utilities->CallLogPop(2545);
11416  return(false);
11417  }
11418  if(NewTrain && (PowerAtRail < 1) && ((Second == "fsp") || (Second == "rsp")))
11419  {
11420  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11421  "': a train created without power can't split.");
11422  Utilities->CallLogPop(2546);
11423  return(false);
11424  }
11425  if(NewTrain && (PowerAtRail < 1) && (Second == "cdt"))
11426  {
11427  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11428  "': a train created without power can't change direction under timetable control.");
11429  Utilities->CallLogPop(2547);
11430  return(false);
11431  }
11432 */
11433  //end of new additions
11434  if(x == 0) // should be start event
11435  {
11436  if(SequenceType != StartSequence)
11437  {
11438  TimetableMessage(GiveMessages, "Error in timetable - First event not a start event: '" + OneEntry + "'");
11439  Utilities->CallLogPop(784);
11440  return(false);
11441  }
11442  if((Second == "Snt") && (Fourth == 'S') && (NewRemainder != ""))
11443  {
11444  if(NewRemainder[1] != 'R')
11445  {
11446  TimetableMessage(GiveMessages,
11447  "Error in timetable - the only event that can follow a train created under signaller control is a repeat, see '" +
11448  OneEntry + "'");
11449  Utilities->CallLogPop(787);
11450  return(false);
11451  }
11452  }
11453  if((Second == "Snt") || (Second == "Snt-sh"))
11454  // need full train information including non-default values for at least HeadCode, Description,
11455  // MaxRunningSpeed, Mass, MaxBrakeRate, & PowerAtRail
11456  {
11457  if((HeadCode == "") || (Description == "") || (MaxRunningSpeed == 0) || (Mass == 0) || (MaxBrakeRate == 0)) // ||
11458  // (PowerAtRail == 0)) allowed 0 for power at v2.4.0
11459  {
11460  TimetableMessage(GiveMessages, "Error in timetable - train information incomplete before 'Snt' or 'Snt-sh' start event: '" +
11461  OneEntry + "'");
11462  Utilities->CallLogPop(1783);
11463  return(false);
11464  }
11465  }
11466  if((Second == "Sfs") || (Second == "Sns") || (Second == "Sns-sh") || (Second == "Sns-fsh"))
11467  // service continuation - need at least non-default value for HeadCode
11468  {
11469  if(HeadCode == "")
11470  {
11471  TimetableMessage(GiveMessages, "Error in timetable - headcode missing before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
11472  OneEntry + "'");
11473  Utilities->CallLogPop(788);
11474  return(false);
11475  }
11476  if((StartSpeed != 0) || (MaxRunningSpeed != 0) || (Mass != 0) || (MaxBrakeRate != 0) || (PowerAtRail != 0))
11477  {
11478  TimetableMessage(GiveMessages,
11479  "Error in timetable - information additional to a headcode & optional description given before 'Sfs', 'Sns', 'Sns-sh' or 'Sns-fsh' start event: '" +
11480  OneEntry + "'");
11481  Utilities->CallLogPop(843);
11482  return(false);
11483  }
11484  }
11485  }
11486  if(SequenceType == FinishSequence)
11487  {
11488  FinishFlag = true;
11489  // marker for only permitted additional entry being a repeat, only needed if the
11490  // finish entry is not the last entry
11491  }
11492  if(FinalCall)
11493  {
11494  // interpret & add to ActionVector
11495  TDateTime TempTime;
11496  TActionVectorEntry ActionVectorEntry;
11497  ActionVectorEntry.FormatType = FormatType;
11498  ActionVectorEntry.LocationType = LocationType;
11499  ActionVectorEntry.SequenceType = SequenceType;
11500  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
11501  ActionVectorEntry.Warning = Warning;
11502  if(FormatType == TimeLoc)
11503  {
11504  if(CheckTimeValidity(1, First, ActionVectorEntry.EventTime))
11505  {
11506  ;
11507  } // these will all be true as final call
11508 
11509  ActionVectorEntry.LocationName = Second;
11510  }
11511  else if(FormatType == PassTime) // new
11512  {
11513  if(CheckTimeValidity(17, First, ActionVectorEntry.EventTime))
11514  {
11515  ;
11516  }
11517  ActionVectorEntry.Command = Second;
11518  ActionVectorEntry.LocationName = Third;
11519  }
11520  else if(FormatType == TimeTimeLoc)
11521  {
11522  if(CheckTimeValidity(2, First, ActionVectorEntry.ArrivalTime))
11523  {
11524  ;
11525  }
11526  if(CheckTimeValidity(3, Second, ActionVectorEntry.DepartureTime))
11527  {
11528  ;
11529  }
11530  ActionVectorEntry.LocationName = Third;
11531  }
11532  else if(FormatType == TimeCmd)
11533  {
11534  if(CheckTimeValidity(4, First, ActionVectorEntry.EventTime))
11535  {
11536  ;
11537  }
11538  ActionVectorEntry.Command = Second;
11539  }
11540  else if(FormatType == ExitRailway)
11541  {
11542  if(CheckTimeValidity(18, First, ActionVectorEntry.EventTime))
11543  {
11544  ;
11545  }
11546  ActionVectorEntry.Command = Second;
11547  ActionVectorEntry.ExitList = ExitList;
11548  }
11549  else if(FormatType == StartNew)
11550  {
11551  if(CheckTimeValidity(5, First, ActionVectorEntry.EventTime))
11552  {
11553  ;
11554  }
11555  ActionVectorEntry.Command = Second;
11556  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11557  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11558  if(Fourth == 'S')
11559  {
11560  ActionVectorEntry.SignallerControl = true;
11561  }
11562  }
11563  else if(FormatType == SNTShuttle)
11564  {
11565  if(CheckTimeValidity(6, First, ActionVectorEntry.EventTime))
11566  {
11567  ;
11568  }
11569  ActionVectorEntry.Command = Second;
11570  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11571  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11572  ActionVectorEntry.OtherHeadCode = Fourth;
11573  }
11574  else if(FormatType == SNSShuttle)
11575  {
11576  if(CheckTimeValidity(7, First, ActionVectorEntry.EventTime))
11577  {
11578  ;
11579  }
11580  ActionVectorEntry.Command = Second;
11581  ActionVectorEntry.OtherHeadCode = Third;
11582  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
11583  }
11584  else if(FormatType == TimeCmdHeadCode) //fsp/rsp is TimeCmdHeadCode but may have a Fourth - see below
11585  {
11586  if(CheckTimeValidity(8, First, ActionVectorEntry.EventTime))
11587  {
11588  ;
11589  }
11590  ActionVectorEntry.Command = Second;
11591  ActionVectorEntry.OtherHeadCode = Third;
11592  if((Second == "fsp") || (Second == "rsp")) //new at v2.15.0 & checked in SplitEntry
11593  {
11594  ActionVectorEntry.SplitDistribution = "50-50"; //added at v2.18.0
11595  if(Fourth != "")
11596  {
11597  ActionVectorEntry.SplitDistribution = Fourth;
11598  }
11599  }
11600  }
11601  else if((FormatType == FNSNonRepeatToShuttle) || (FormatType == SNSNonRepeatFromShuttle))
11602  {
11603  if(CheckTimeValidity(9, First, ActionVectorEntry.EventTime))
11604  {
11605  ;
11606  }
11607  ActionVectorEntry.Command = Second;
11608  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
11609  }
11610  else if(FormatType == FSHNewService)
11611  {
11612  if(CheckTimeValidity(10, First, ActionVectorEntry.EventTime))
11613  {
11614  ;
11615  }
11616  ActionVectorEntry.Command = Second;
11617  ActionVectorEntry.OtherHeadCode = Third;
11618  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
11619  }
11620  else if(FormatType == FinRemHere)
11621  {
11622  ActionVectorEntry.Command = Second;
11623  }
11624  else if(FormatType == TimeCmdDescription) //new at v2.15.0
11625  {
11626  if(CheckTimeValidity(35, First, ActionVectorEntry.EventTime))
11627  {
11628  ;
11629  }
11630  ActionVectorEntry.Command = Second;
11631  ActionVectorEntry.NewDescription = Third;
11632  }
11633  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
11634  }
11635  }
11636  else // last entry, & not entered under signaller control with no repeats, i.e. could be finish or repeat
11637  {
11638  OneEntry = NewRemainder;
11639  First = "";
11640  Second = "";
11641  Third = "";
11642  Fourth = "";
11643  RearStartOrRepeatMins = 0;
11644  FrontStartOrRepeatDigits = 0;
11645  NumberOfRepeats = 0;
11646  if((FinishFlag) && (OneEntry[1] != 'R'))
11647  // already had a finish entry
11648  {
11649  TimetableMessage(GiveMessages, "Error in timetable - Last event = '" + OneEntry + "'. An earlier finish event has been found with something other than a repeat following it - only a repeat can follow a finish event.");
11650  Utilities->CallLogPop(79);
11651  return(false);
11652  }
11653  if(OneEntry[1] != 'R') // must be finish
11654  {
11655  if(!SplitEntry(1, OneEntry, GiveMessages, CheckLocationsExistInRailway, First, Second, Third, Fourth, RearStartOrRepeatMins,
11656  FrontStartOrRepeatDigits, FormatType, LocationType, SequenceType, ShuttleLinkType, ExitList, Warning))
11657  {
11658  TimetableMessage(GiveMessages, "Error in timetable - Event: '" + OneEntry + "'.\nIf the program version is not the latest the "
11659  "timetable may have features that aren't compatible with the version in use.");
11660  Utilities->CallLogPop(757);
11661  return(false);
11662  }
11663  //below added at v2.14.0 to prevent unpowered trains attempting to form a new service.
11664 /* restriction removed at v2.19.0
11665  if(NewTrain && (PowerAtRail < 1) && ((Second == "Fns") || (Second == "Frh-sh") || (Second == "Fns-sh") || (Second == "F-nshs")))
11666  {
11667  TimetableMessage(GiveMessages, "Error in line - '" + OneEntry +
11668  "': a train created without power can't form a new service.");
11669  Utilities->CallLogPop(2548);
11670  return(false);
11671  }
11672  //end of new additions
11673 */
11674  if(SequenceType != FinishSequence)
11675  {
11676  TimetableMessage(GiveMessages, "Error in timetable - last event should be a finish: '" + OneEntry + "'");
11677  Utilities->CallLogPop(785);
11678  return(false);
11679  }
11680  if(FinalCall)
11681  {
11682  // interpret & add to ActionVector
11683  TDateTime TempTime;
11684  TActionVectorEntry ActionVectorEntry;
11685  ActionVectorEntry.FormatType = FormatType;
11686  ActionVectorEntry.LocationType = LocationType;
11687  ActionVectorEntry.SequenceType = SequenceType;
11688  ActionVectorEntry.ShuttleLinkType = ShuttleLinkType;
11689  ActionVectorEntry.Warning = Warning;
11690  if(FormatType == TimeCmd)
11691  {
11692  if(CheckTimeValidity(11, First, ActionVectorEntry.EventTime))
11693  {
11694  ;
11695  }
11696  ActionVectorEntry.Command = Second;
11697  }
11698  else if(FormatType == TimeCmdHeadCode)
11699  {
11700  if(CheckTimeValidity(12, First, ActionVectorEntry.EventTime))
11701  {
11702  ;
11703  }
11704  ActionVectorEntry.Command = Second;
11705  ActionVectorEntry.OtherHeadCode = Third;
11706  }
11707  else if(FormatType == FNSNonRepeatToShuttle)
11708  {
11709  if(CheckTimeValidity(13, First, ActionVectorEntry.EventTime))
11710  {
11711  ;
11712  }
11713  ActionVectorEntry.Command = Second;
11714  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Third;
11715  }
11716  else if(FormatType == FSHNewService)
11717  {
11718  if(CheckTimeValidity(14, First, ActionVectorEntry.EventTime))
11719  {
11720  ;
11721  }
11722  ActionVectorEntry.Command = Second;
11723  ActionVectorEntry.OtherHeadCode = Third;
11724  ActionVectorEntry.NonRepeatingShuttleLinkHeadCode = Fourth;
11725  }
11726  else if(FormatType == ExitRailway)
11727  {
11728  if(CheckTimeValidity(19, First, ActionVectorEntry.EventTime))
11729  {
11730  ;
11731  }
11732  ActionVectorEntry.Command = Second;
11733  ActionVectorEntry.ExitList = ExitList;
11734  }
11735  else if(FormatType == FinRemHere)
11736  {
11737  ActionVectorEntry.Command = Second;
11738  }
11739  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
11740  }
11741  }
11742  else // repeat
11743  {
11744  if(!SplitRepeat(0, OneEntry, RearStartOrRepeatMins, FrontStartOrRepeatDigits, NumberOfRepeats, GiveMessages))
11745  {
11746  Utilities->CallLogPop(786);
11747  // error messages given in SplitRepeat
11748  return(false);
11749  }
11750  if(FinalCall)
11751  {
11752  TActionVectorEntry ActionVectorEntry;
11753  ActionVectorEntry.FormatType = Repeat;
11754  ActionVectorEntry.LocationType = LocTypeForRepeatEntry;
11755  ActionVectorEntry.SequenceType = SequTypeForRepeatEntry;
11756  ActionVectorEntry.ShuttleLinkType = ShuttleLinkTypeForRepeatEntry;
11757  ActionVectorEntry.RearStartOrRepeatMins = RearStartOrRepeatMins;
11758  ActionVectorEntry.FrontStartOrRepeatDigits = FrontStartOrRepeatDigits;
11759  ActionVectorEntry.NumberOfRepeats = NumberOfRepeats;
11760  TempTrainDataEntry.ActionVector.push_back(ActionVectorEntry);
11761  }
11762  }
11763  }
11764  }
11765  if(FinalCall)
11766  {
11767  TrainDataVector.push_back(TempTrainDataEntry);
11768  }
11769  }
11770  Utilities->CallLogPop(80);
11771  return(true);
11772 }
11773 
11774 // ---------------------------------------------------------------------------
11775 
11776 bool TTrainController::Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
11777 {
11778  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",Last2CharactersBothDigits," + HeadCode);
11779  if((HeadCode[HeadCode.Length() - 1] < '0') || (HeadCode[HeadCode.Length() - 1] > '9'))
11780  {
11781  Utilities->CallLogPop(1890);
11782  return(false);
11783  }
11784  if((HeadCode[HeadCode.Length()] < '0') || (HeadCode[HeadCode.Length()] > '9'))
11785  {
11786  Utilities->CallLogPop(1891);
11787  return(false);
11788  }
11789  Utilities->CallLogPop(1892);
11790  return(true);
11791 }
11792 
11793 // ---------------------------------------------------------------------------
11794 
11795 bool TTrainController::CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
11796 // 1st 5 chars must be HH:MM, anything else will be ignored
11797 {
11798  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckTimeValidity," + TimeStr);
11799  if(TimeStr.Length() < 5)
11800  {
11801  Utilities->CallLogPop(926);
11802  return(false);
11803  }
11804  if((TimeStr[1] < '0') || (TimeStr[1] > '9'))
11805  {
11806  Utilities->CallLogPop(927);
11807  return(false);
11808  }
11809  if((TimeStr[2] < '0') || (TimeStr[2] > '9'))
11810  {
11811  Utilities->CallLogPop(928);
11812  return(false);
11813  }
11814  if(TimeStr[3] != ':')
11815  {
11816  Utilities->CallLogPop(929);
11817  return(false);
11818  }
11819  if((TimeStr[4] < '0') || (TimeStr[4] > '5'))
11820  {
11821  Utilities->CallLogPop(930);
11822  return(false);
11823  }
11824  if((TimeStr[5] < '0') || (TimeStr[5] > '9'))
11825  {
11826  Utilities->CallLogPop(931);
11827  return(false);
11828  }
11829  while(TimeStr.Length() > 5)
11830  {
11831  TimeStr = TimeStr.SubString(1, TimeStr.Length() - 1);
11832  }
11833  double WholeHours = (AnsiString(TimeStr[1]) + AnsiString(TimeStr[2])).ToDouble();
11834  double FracHour = ((AnsiString(TimeStr[4]) + AnsiString(TimeStr[5])).ToDouble()) / 60.0;
11835 
11836  if((WholeHours + FracHour) >= 95.98334)
11837  {
11838  Utilities->CallLogPop(1817);
11839  return(false); // > 95h 59m
11840  }
11841  Time = TDateTime((WholeHours + FracHour) / 24);
11842  Utilities->CallLogPop(932);
11843  return(true);
11844 }
11845 
11846 // ---------------------------------------------------------------------------
11847 
11848 bool TTrainController::SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second,
11849  AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, TTimetableFormatType &FormatType,
11850  TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
11851 /* This is a train action entry from a single line of the timetable, i.e. not train information and not a repeat entry.
11852  Return false for failure.
11853  See description above under ProcessOneTimetableLinefor details of train action entries
11854  NB all types set except LocationType for Snt as may be located or not
11855 */{
11856  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitEntry," + OneEntry);
11857  Warning = false;
11858  TDateTime TempTime;
11859 
11860  if(OneEntry.Length() > 0)
11861  {
11862  if(OneEntry[1] == 'W') // warning
11863  {
11864  Warning = true;
11865  OneEntry = OneEntry.SubString(2, OneEntry.Length() - 1);
11866  // strip it off
11867  }
11868  }
11869  if(OneEntry == "Frh")
11870  {
11871  FormatType = FinRemHere;
11872  SequenceType = FinishSequence;
11873  LocationType = AtLocation;
11874  ShuttleLinkType = NotAShuttleLink;
11875  Second = "Frh";
11876  Utilities->CallLogPop(1016);
11877  return(true);
11878  }
11879  if(OneEntry.Length() < 7)
11880  {
11881  Utilities->CallLogPop(907);
11882  return(false); // 'HH:MM;' + at least a one-letter location name
11883  }
11884  int Pos = OneEntry.Pos(';'); // first segment delimiter
11885 
11886  if(Pos != 6)
11887  {
11888  Utilities->CallLogPop(908);
11889  return(false);
11890  // no delimiter or delimiter not in position 6, has to be a time so fail
11891  }
11892  First = OneEntry.SubString(1, 5); // has to be a time
11893  if(!CheckTimeValidity(16, First, TempTime))
11894  {
11895  Utilities->CallLogPop(909);
11896  return(false);
11897  }
11898  AnsiString Remainder = OneEntry.SubString(Pos + 1, OneEntry.Length() - Pos);
11899 
11900 // if((Remainder[1] >= '0') && (Remainder[1] <= '9')) changed at v2.16.0 so only 'digit-digit-colon....' interpreted as a time - to allow locations to begin with digits
11901  if((Remainder.Length() >= 3) && (Remainder[1] >= '0') && (Remainder[1] <= '9') && (Remainder[2] >= '0') && (Remainder[2] <= '9') && (Remainder[3] == ':'))
11902  // next segment is a time so this is a TimeTimeLoc & 3rd seg has to be a location to be valid
11903  {
11904  if(Remainder.Length() < 7)
11905  {
11906  Utilities->CallLogPop(910);
11907  return(false); // 'HH:MM;' + at least a one-letter location name
11908  }
11909  Pos = Remainder.Pos(';'); // second segment delimiter
11910  if(Pos == 0)
11911  {
11912  Utilities->CallLogPop(911);
11913  return(false);
11914  // no delimiter, has to be one between departure time & location
11915  }
11916  Second = Remainder.SubString(1, 5); // has to be a time
11917  if(!CheckTimeValidity(15, Second, TempTime))
11918  {
11919  Utilities->CallLogPop(912);
11920  return(false);
11921  }
11922  Third = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
11923  if(!CheckLocationValidity(0, Third, GiveMessages, CheckLocationsExistInRailway))
11924  {
11925  Utilities->CallLogPop(913);
11926  return(false);
11927  }
11928  FormatType = TimeTimeLoc;
11929  SequenceType = IntermediateSequence;
11930  LocationType = AtLocation;
11931  ShuttleLinkType = NotAShuttleLink;
11932  Utilities->CallLogPop(914);
11933  return(true);
11934  }
11935  Pos = Remainder.Pos(';'); // second segment delimiter
11936  if(Pos == 0) // no third segment so second must be a location, or cdt
11937  {
11938  Second = Remainder;
11939  if(Second == "cdt")
11940  {
11941  FormatType = TimeCmd;
11942  ShuttleLinkType = NotAShuttleLink;
11943  LocationType = AtLocation;
11944  SequenceType = IntermediateSequence;
11945  Utilities->CallLogPop(915);
11946  return(true);
11947  }
11948  if(!CheckLocationValidity(1, Second, GiveMessages, CheckLocationsExistInRailway))
11949  {
11950  Utilities->CallLogPop(916);
11951  return(false);
11952  }
11953  else
11954  {
11955  FormatType = TimeLoc;
11956  LocationType = AtLocation;
11957  SequenceType = IntermediateSequence;
11958  ShuttleLinkType = NotAShuttleLink;
11959  Utilities->CallLogPop(917);
11960  return(true);
11961  }
11962  }
11963  // here if second segment is a command, with a third & maybe fourth segments
11964  if((Pos != 4) && (Pos != 7) && (Pos != 8))
11965  {
11966  Utilities->CallLogPop(918);
11967  return(false);
11968  // no third segement or not in position 4 or 7, & should be since all commands are 3, 6 or 7 letters
11969  }
11970  Second = Remainder.SubString(1, Pos - 1); // command
11971 
11972  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
11973  // details
11974  Pos = Remainder.Pos(';'); // third segment delimiter
11975  if(Pos == 0)
11976  {
11977  Third = Remainder;
11978  }
11979  else
11980  {
11981  Third = Remainder.SubString(1, Pos - 1);
11982  Fourth = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
11983  }
11984 
11985  if((Second == "Snt") || (Second == "Snt-sh"))
11986  // third has to be 2 element idents with a space between
11987  {
11988  int SpacePos = Third.Pos(' ');
11989  if(SpacePos == 0)
11990  {
11991  Utilities->CallLogPop(919);
11992  return(false); // no space
11993  }
11994  AnsiString RearStartStr = Third.SubString(1, SpacePos - 1);
11995  AnsiString FrontStartStr = Third.SubString(SpacePos + 1, Third.Length() - SpacePos);
11996  // int RearPosition=0, FrontPosition=0, RearExitPos=0;
11997  if(CheckLocationsExistInRailway)
11998  {
11999  if(!CheckStartPositionValidity(0, RearStartStr, FrontStartStr, GiveMessages))
12000  {
12001  Utilities->CallLogPop(920);
12002  return(false);
12003  }
12004  RearStartOrRepeatMins = Track->GetTrackVectorPositionFromString(3, RearStartStr, GiveMessages);
12005  FrontStartOrRepeatDigits = Track->GetTrackVectorPositionFromString(4, FrontStartStr, GiveMessages);
12006  }
12007  if(Second == "Snt")
12008  {
12009  FormatType = StartNew;
12010  SequenceType = StartSequence;
12011  LocationType = NoLocation;
12012  // can't be set until know whether located or not - done in SecondPassActions
12013  ShuttleLinkType = NotAShuttleLink;
12014  }
12015  else // Snt-sh
12016  {
12017  FormatType = SNTShuttle;
12018  LocationType = AtLocation;
12019  SequenceType = StartSequence;
12020  ShuttleLinkType = ShuttleLink;
12021  if(!CheckHeadCodeValidity(0, GiveMessages, Fourth))
12022  {
12023  Utilities->CallLogPop(1038);
12024  return(false);
12025  }
12026  }
12027  Utilities->CallLogPop(921);
12028  return(true);
12029  }
12030  if(Second == "Sns-sh") // third & fourth have to be headcodes
12031  {
12032  FormatType = SNSShuttle;
12033  LocationType = AtLocation;
12034  SequenceType = StartSequence;
12035  ShuttleLinkType = ShuttleLink;
12036  if(!CheckHeadCodeValidity(1, GiveMessages, Third))
12037  {
12038  Utilities->CallLogPop(1039);
12039  return(false);
12040  }
12041  if(!CheckHeadCodeValidity(2, GiveMessages, Fourth))
12042  {
12043  Utilities->CallLogPop(1040);
12044  return(false);
12045  }
12046  Utilities->CallLogPop(1041);
12047  return(true);
12048  }
12049  if(Second == "F-nshs")
12050  {
12051  FormatType = FNSNonRepeatToShuttle;
12052  LocationType = AtLocation;
12053  SequenceType = FinishSequence;
12054  ShuttleLinkType = ShuttleLink;
12055  if(!CheckHeadCodeValidity(3, GiveMessages, Third))
12056  {
12057  Utilities->CallLogPop(1047);
12058  return(false);
12059  }
12060  Utilities->CallLogPop(1048);
12061  return(true);
12062  }
12063  if(Second == "Sns-fsh")
12064  {
12065  FormatType = SNSNonRepeatFromShuttle;
12066  LocationType = AtLocation;
12067  SequenceType = StartSequence;
12068  ShuttleLinkType = ShuttleLink;
12069  if(!CheckHeadCodeValidity(4, GiveMessages, Third))
12070  {
12071  Utilities->CallLogPop(1098);
12072  return(false);
12073  }
12074  Utilities->CallLogPop(1099);
12075  return(true);
12076  }
12077  if(Second == "Fns-sh") // third & fourth have to be headcodes
12078  {
12079  FormatType = FSHNewService;
12080  LocationType = AtLocation;
12081  SequenceType = FinishSequence;
12082  ShuttleLinkType = ShuttleLink;
12083  if(!CheckHeadCodeValidity(5, GiveMessages, Third))
12084  {
12085  Utilities->CallLogPop(1050);
12086  return(false);
12087  }
12088  if(!CheckHeadCodeValidity(6, GiveMessages, Fourth))
12089  {
12090  Utilities->CallLogPop(1051);
12091  return(false);
12092  }
12093  Utilities->CallLogPop(1052);
12094  return(true);
12095  }
12096  // new segment for 'pas'
12097  if(Second == "pas") // third has to be a location
12098  {
12099  FormatType = PassTime;
12100  LocationType = EnRoute;
12101  SequenceType = IntermediateSequence;
12102  ShuttleLinkType = NotAShuttleLink;
12103  if(!CheckLocationValidity(2, Third, GiveMessages, CheckLocationsExistInRailway))
12104  {
12105  Utilities->CallLogPop(1515);
12106  return(false);
12107  }
12108  Utilities->CallLogPop(1516);
12109  return(true);
12110  }
12111  // new segment for revised 'Fer'
12112  if(Second == "Fer")
12113  // third has to be a set of IDs separated by spaces, and at least 1
12114  {
12115  FormatType = ExitRailway;
12116  LocationType = EnRoute;
12117  SequenceType = FinishSequence;
12118  ShuttleLinkType = NotAShuttleLink;
12119  if(CheckLocationsExistInRailway)
12120  {
12121  if(!CheckAndPopulateListOfIDs(0, Third, ExitList, GiveMessages))
12122  {
12123  Utilities->CallLogPop(1519);
12124  return(false);
12125  }
12126  }
12127  Utilities->CallLogPop(1520);
12128  return(true);
12129  }
12130  if(Second == "dsc") //new at v2.15.0 - change description
12131  {
12132  if(Third.Length() > 60)
12133  {
12134  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters in '" + Third + "'");
12135  Utilities->CallLogPop(2582);
12136  return(false);
12137  }
12138  for(int x = 1; x < Third.Length() + 1; x++)
12139  {
12140 // if((Third[x] < ' ') || (Third[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12141  if((Third[x] < ' ') && (Third[x] >= 0))
12142  {
12143  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + Third + "'");
12144  Utilities->CallLogPop(2583);
12145  return(false);
12146  }
12147  }
12148  FormatType = TimeCmdDescription;
12149  LocationType = AtLocation;
12150  SequenceType = IntermediateSequence;
12151  ShuttleLinkType = NotAShuttleLink;
12152  Utilities->CallLogPop(2604);
12153  return(true);
12154  }
12155 
12156 // if((Second == "fsp") || (Second == "fsp")) then there can optionlly be a fourth: xx-yy where xx = percentage mass & yy = percentage power in the split train
12157 
12158  // all remainder must be TimeCmdHeadCode types to be valid
12159  if((Second != "Fns") && (Second != "Fjo") && (Second != "jbo") && (Second != "fsp") && (Second != "rsp") && (Second != "Sfs") && (Second != "Sns") &&
12160  (Second != "Frh-sh"))
12161  {
12162  Utilities->CallLogPop(922);
12163  return(false); // all TimeCmdHeadCode types
12164  }
12165  if(!CheckHeadCodeValidity(7, GiveMessages, Third))
12166  {
12167  Utilities->CallLogPop(923);
12168  return(false);
12169  }
12170  FormatType = TimeCmdHeadCode;
12171  LocationType = AtLocation;
12172  if(Second == "Frh-sh")
12173  {
12174  ShuttleLinkType = ShuttleLink;
12175  }
12176  else
12177  {
12178  ShuttleLinkType = NotAShuttleLink;
12179  }
12180  if((Second == "Fns") || (Second == "Fjo") || (Second == "Frh-sh"))
12181  {
12182  SequenceType = FinishSequence;
12183  }
12184  if((Second == "jbo") || (Second == "fsp") || (Second == "rsp"))
12185  {
12186  SequenceType = IntermediateSequence;
12187  }
12188  if((Second == "Sfs") || (Second == "Sns"))
12189  {
12190  SequenceType = StartSequence;
12191  }
12192  //new at v2.15.0 to allow splits to have different characteristics - Fourth specifies: AA-BB where AA & BB can be 1 or 2 digits, AA is percentage mass
12193  if((Fourth != "") && ((Second == "fsp") || (Second == "rsp"))) //& BB is percentage power allocated to the split off train
12194  {
12195  if(!CheckFourthValidityForSplit(Fourth, GiveMessages))
12196  {
12197  Utilities->CallLogPop(2584);
12198  return(false);
12199  }
12200  }
12201  Utilities->CallLogPop(924);
12202  return(true);
12203 }
12204 
12205 // ---------------------------------------------------------------------------
12206 
12207 bool TTrainController::CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages) //new at v2.15.0
12208 {
12209  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",CheckFourthValidityForSplit," + SplitDistributionString);
12210  bool ErrorFlag = false;
12211  int x, y;
12212  if((SplitDistributionString.Length() > 6 ) || (SplitDistributionString.Length() < 3))
12213  {
12214  ErrorFlag = true;
12215  }
12216  int pos = SplitDistributionString.Pos('-');
12217  if(pos == 0)
12218  {
12219  ErrorFlag = true;
12220  }
12221  else
12222  {
12223  AnsiString MassStr = SplitDistributionString.SubString(1, pos - 1);
12224  AnsiString PowerStr = SplitDistributionString.SubString(pos + 1, SplitDistributionString.Length() - pos);
12225  try //allows for one or two digit percentages
12226  {
12227  int x = MassStr.ToInt();
12228  int y = PowerStr.ToInt();
12229  if((x > 99) || (x < 1) || (y > 100) || (y < 0))
12230  {
12231  ErrorFlag = true;
12232  }
12233  }
12234  catch(const Exception &e) //non-error catch
12235  {
12236  ErrorFlag = true;
12237  }
12238  }
12239  if(ErrorFlag)
12240  {
12241  TimetableMessage(GiveMessages, "Error in split distribution " + SplitDistributionString + ", should be 'AA-BB' where AA is the percentage mass (min 1, max 99) and BB the percentage " +
12242  "power for the new split-off train");
12243  Utilities->CallLogPop(2585);
12244  return(false);
12245  }
12246  Utilities->CallLogPop(2601);
12247  return(true);
12248 }
12249 
12250 // ---------------------------------------------------------------------------
12251 
12252 bool TTrainController::CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
12253 {
12254  // check that the location name exists in the railway (only if CheckLocationsExistInRailway is true), doesn't begin with 'digit-digit-colon'
12255  // and contains no control characters changed at v2.16.0 to allow extended characters in location names
12256  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckLocationValidity," + LocStr);
12257  if(LocStr == "")
12258  {
12259  Utilities->CallLogPop(1353);
12260  return(false); // has to have at least one character
12261  }
12262 // if((LocStr[1] >= '0') && (LocStr[1] <= '9')) //changed at v2.16.0 to allow locations to begine with digits, if 'digit-digit-colon' then must be a time
12263  if((LocStr.Length() >= 3) && (LocStr[1] >= '0') && (LocStr[1] <= '9') && (LocStr[2] >= '0') && (LocStr[2] <= '9') && (LocStr[3] == ':'))
12264  {
12265  Utilities->CallLogPop(1354);
12266  return(false); // can't begin with 'digit-digit-colon' as this regarded as a time
12267  }
12268  for(int x = 1; x < LocStr.Length() + 1; x++)
12269  {
12270  if(((LocStr[x] < ' ') && (LocStr[x] >= 0)) || (LocStr[x] == ',') || (LocStr[x] == ';')) //changed at v2.16.0 to allow extended characters in location names
12271  {
12272  Utilities->CallLogPop(1355);
12273  return(false); // contains a special character or ',' or ';'
12274  }
12275 /*
12276  if(LocStr[x] > 'z') //dropped at v2.16.0 to allow extended characters in location names
12277  {
12278  Utilities->CallLogPop(1356);
12279  return(false); // contains a character outside the standard ASCII set
12280  }
12281 */
12282  }
12283  // check exists in railway location list if CheckLocationsExistInRailway is true
12284  if(CheckLocationsExistInRailway)
12285  {
12286  if(!Track->TimetabledLocationNameAllocated(3, LocStr))
12287  {
12288  TimetableMessage(GiveMessages, "Location name '" + LocStr +
12289  "' appears in the timetable but is not a valid name. To be valid the name must be a stopping location and apply to one or more platforms " +
12290  "(not concourses on their own), or to track at a blue non-station named location. BUT NOTE THAT trains can't stop at continuations so a name " +
12291  "that includes a continuation will not be valid.");
12292  Utilities->CallLogPop(1357);
12293  return(false);
12294  }
12295  }
12296  Utilities->CallLogPop(1358);
12297  return(true);
12298 }
12299 
12300 // ---------------------------------------------------------------------------
12301 
12302 bool TTrainController::CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
12303 {
12304  // if(!AnyHeadCodeValid) up to 8 characters total & last 4 characters must be NLNN where N = number and L = capital or small letter
12305  // if(AnyHeadCodeValid) up to 8 characters total, last 2 chars must be digits & last but 2 can be any alphanumeric, upper or lower case
12306  // NOTE: As of v0.6b AnyHeadCodeValid dropped, all headcodes are unrestricted
12307  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + "," + AnsiString((short)GiveMessages) + ",CheckHeadCodeValidity," +
12308  HeadCode);
12309  if((HeadCode.Length() < 4) || (HeadCode.Length() > 8))
12310  {
12311  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode +
12312  "', length must be between 4 and 8 characters, and last 4 must be a legitimate headcode. This error can also be caused by omitting a service reference after Sns, Snt-sh, Sns-sh, Fns, Fns-sh or Frh-sh");
12313  Utilities->CallLogPop(1359);
12314  return(false);
12315  }
12316  // firstly allow any printable character (ASCII >= CHAR(32) & <= CHAR(126)), as these allowed in 1st 4 characters
12317  for(int x = 1; x < (HeadCode.Length() + 1); x++)
12318  {
12319  if((HeadCode[x] < ' ') || (HeadCode[x] > '~'))
12320  {
12321  TimetableMessage(GiveMessages, "Non-printable character in headcode '" + HeadCode + "'");
12322  Utilities->CallLogPop(1895);
12323  return(false);
12324  }
12325  }
12326  // secondly ensure the true Headcode only has letters or digits
12327  for(int x = 3; x >= 0; x--)
12328  {
12329  if(((HeadCode[HeadCode.Length() - x] < 'A') || (HeadCode[HeadCode.Length() - x] > 'Z')) && ((HeadCode[HeadCode.Length() - x] < 'a') ||
12330  (HeadCode[HeadCode.Length() - x] > 'z')) && ((HeadCode[HeadCode.Length() - x] < '0') || (HeadCode[HeadCode.Length() - x] > '9')))
12331  {
12332  TimetableMessage(GiveMessages, "Headcode error in '" + HeadCode + "', headcode must consist of letters and digits only");
12333  Utilities->CallLogPop(1790);
12334  return(false);
12335  }
12336  }
12337  Utilities->CallLogPop(1364);
12338  return(true);
12339 }
12340 
12341 // ---------------------------------------------------------------------------
12342 
12343 bool TTrainController::CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
12344 // set of track element IDs, separated by spaces, and at least 1 present
12345 {
12346  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckAndPopulateListOfIDs," + IDSet); //had wrong title, changed at v2.13.0
12347  ExitList.clear();
12348  AnsiString CurrentID = "";
12349 
12350  if(IDSet.Length() == 0)
12351  {
12352  TimetableMessage(GiveMessages, "Must have at least one exit element ID following 'Fer'");
12353  Utilities->CallLogPop(1521);
12354  return(false);
12355  }
12356  for(int x = 1; x <= IDSet.Length(); x++)
12357  {
12358  char C = IDSet[x];
12359  if(((C < '0') || (C > '9')) && (C != ' ') && (C != 'N') && (C != '-'))
12360  {
12361  TimetableMessage(GiveMessages, "Illegal character in the set of element IDs following 'Fer' in '" + IDSet + "'");
12362  Utilities->CallLogPop(1522);
12363  return(false);
12364  }
12365 /* don't use, error checks in GetTrackVectorPositionFromString instead
12366  if(C == '-') //this section added at v2.13.0 because of Amon Sadler's error file submitted 24/03/22
12367  {
12368  if((x==1) || (x == IDSet.Length()))
12369  {
12370  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
12371  Utilities->CallLogPop(2479);
12372  return(false);
12373  }
12374  if((IDSet[x-1] < '0') || (IDSet[x-1] > '9') || (IDSet[x+1] < '0') || (IDSet[x+1] > '9'))
12375  {
12376  TimetableMessage(GiveMessages, "Illegal minus character ('-') in the set of element IDs following 'Fer' in '" + IDSet + "'");
12377  Utilities->CallLogPop(2480);
12378  return(false);
12379  }
12380  }
12381 */
12382  }
12383  int Pos = IDSet.Pos(' '); // look for the first space
12384 
12385  while(true)
12386  {
12387  if(Pos == 0)
12388  {
12389  CurrentID = IDSet;
12390  IDSet = "";
12391  }
12392  else
12393  {
12394  CurrentID = IDSet.SubString(1, Pos - 1);
12395  IDSet = IDSet.SubString(Pos + 1, IDSet.Length() - Pos);
12396  }
12397  int VecPos = Track->GetTrackVectorPositionFromString(7, CurrentID, GiveMessages);
12398  if(VecPos == -1)
12399  {
12400  Utilities->CallLogPop(1523);
12401  return(false); // messages given in GetTrackVectorPositionFromString
12402  }
12403  else
12404  {
12405  if(Track->TrackElementAt(722, VecPos).TrackType != Continuation)
12406  {
12407  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' is not an exit");
12408  Utilities->CallLogPop(1524);
12409  return(false);
12410  }
12411  else
12412  {
12413  // first check for duplicates
12414  if(!ExitList.empty())
12415  {
12416  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
12417  {
12418  if(*ELIT == VecPos)
12419  {
12420  TimetableMessage(GiveMessages, "The element ID '" + CurrentID + "' following 'Fer' duplicates an earlier element");
12421  Utilities->CallLogPop(1532);
12422  return(false);
12423  }
12424  }
12425  }
12426  // of OK add it to the list
12427  ExitList.push_back(VecPos);
12428  }
12429  }
12430  if(IDSet == "")
12431  {
12432  Utilities->CallLogPop(1525);
12433  return(true);
12434  }
12435  else
12436  {
12437  Pos = IDSet.Pos(' '); // look for the next space
12438  }
12439  } // while(true)
12440 }
12441 
12442 // ---------------------------------------------------------------------------
12443 bool TTrainController::SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed,
12444  int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
12445 // 7 or 8 items for a new train (6 or 7 semicolons), for a continuing service only need headcode, though can have a description, if other
12446 // data entered for continuing service then will be ignored - message given to warn user, checks appropriate number of items and validity
12447 // of each item
12448 {
12449  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitTrainInfo," + TrainInfoStr);
12450  int Pos = 0;
12451  AnsiString Remainder = "";
12452  int SemiColonCount = 0;
12453 
12454  for(int x = 1; x < TrainInfoStr.Length() + 1; x++)
12455  {
12456  if(TrainInfoStr[x] == ';')
12457  {
12458  SemiColonCount++;
12459  }
12460  }
12461  if((SemiColonCount != 6) && (SemiColonCount != 7) && (SemiColonCount != 1) && (SemiColonCount != 0))
12462  {
12463  TimetableMessage(GiveMessages, "Error in train information in '" + TrainInfoStr +
12464  "'. Should be headcode + optional description for a continuing service;" +
12465  " or headcode, description, start speed, max running speed, mass, brake force, power (and optional signaller max. speed) for a new service");
12466  Utilities->CallLogPop(880);
12467  return(false);
12468  }
12469  if(SemiColonCount == 0)
12470  {
12471  HeadCode = TrainInfoStr;
12472  if(!CheckHeadCodeValidity(8, GiveMessages, HeadCode))
12473  {
12474  Utilities->CallLogPop(881);
12475  return(false);
12476  }
12477  Utilities->CallLogPop(882);
12478  return(true);
12479  }
12480  if(SemiColonCount == 1) // headcode & description only
12481  {
12482  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
12483  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
12484  Description = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
12485  if(!CheckHeadCodeValidity(9, GiveMessages, HeadCode))
12486  {
12487  Utilities->CallLogPop(883);
12488  return(false);
12489  }
12490  if(Description == "")
12491  {
12492  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
12493  Utilities->CallLogPop(884);
12494  return(false);
12495  }
12496  if(Description.Length() > 60)
12497  {
12498  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
12499  Utilities->CallLogPop(1157);
12500  return(false);
12501  }
12502  for(int x = 1; x < Description.Length() + 1; x++)
12503  {
12504 // if((Description[x] < ' ') || (Description[x] > '~')) changed at v2.16.0 to allow extended characters in location names
12505  if((Description[x] < ' ') && (Description[x] >= 0))
12506  {
12507  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
12508  Utilities->CallLogPop(885);
12509  return(false);
12510  }
12511  }
12512  Utilities->CallLogPop(886);
12513  return(true);
12514  }
12515  // if here must have 6 or 7 semicolons
12516  Pos = TrainInfoStr.Pos(';'); // 1st delimiter
12517  HeadCode = TrainInfoStr.SubString(1, Pos - 1);
12518  Remainder = TrainInfoStr.SubString(Pos + 1, TrainInfoStr.Length() - Pos);
12519  if(!CheckHeadCodeValidity(10, GiveMessages, HeadCode))
12520  {
12521  Utilities->CallLogPop(887);
12522  return(false);
12523  }
12524  Pos = Remainder.Pos(';'); // 2nd delimiter
12525  Description = Remainder.SubString(1, Pos - 1);
12526  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12527  if(Description == "")
12528  {
12529  TimetableMessage(GiveMessages, "Train description missing in '" + TrainInfoStr + "'");
12530  Utilities->CallLogPop(888);
12531  return(false);
12532  }
12533  if(Description.Length() > 60)
12534  {
12535  TimetableMessage(GiveMessages, "Train description too long, limit of 60 characters '" + TrainInfoStr + "'");
12536  Utilities->CallLogPop(1158);
12537  return(false);
12538  }
12539  for(int x = 1; x < Description.Length() + 1; x++)
12540  {
12541 // if((Description[x] < ' ') || (Description[x] > 126)) changed at v2.16.0 to allow extended characters in location names
12542  if((Description[x] < ' ') && (Description[x] >= 0))
12543  {
12544  TimetableMessage(GiveMessages, "Train description contains invalid characters in '" + TrainInfoStr + "'");
12545  Utilities->CallLogPop(889);
12546  return(false);
12547  }
12548  }
12549  Pos = Remainder.Pos(';'); // 3rd delimiter
12550  AnsiString StartSpeedStr = Remainder.SubString(1, Pos - 1);
12551 
12552  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12553  if(StartSpeedStr == "")
12554  {
12555  TimetableMessage(GiveMessages, "Train starting speed missing in '" + TrainInfoStr + "'");
12556  Utilities->CallLogPop(890);
12557  return(false);
12558  }
12559  for(int x = 1; x < StartSpeedStr.Length() + 1; x++)
12560  {
12561  if((StartSpeedStr[x] < '0') || (StartSpeedStr[x] > '9'))
12562  {
12563  TimetableMessage(GiveMessages, "Train start speed contains invalid characters in '" + TrainInfoStr + "'");
12564  Utilities->CallLogPop(891);
12565  return(false);
12566  }
12567  }
12568  StartSpeed = StartSpeedStr.ToInt();
12569  if(StartSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12570  {
12571  StartSpeed = TTrain::MaximumSpeedLimit;
12572  if(!SSHigh) // added at v2.4.0
12573  {
12574  TimetableMessage(GiveMessages, "Train starting speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
12575  SSHigh = true;
12576  }
12577  }
12578  Pos = Remainder.Pos(';'); // 4th delimiter
12579  AnsiString MaxRunningSpeedStr = Remainder.SubString(1, Pos - 1);
12580 
12581  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12582  if(MaxRunningSpeedStr == "")
12583  {
12584  TimetableMessage(GiveMessages, "Train maximum running speed missing in '" + TrainInfoStr + "'");
12585  Utilities->CallLogPop(892);
12586  return(false);
12587  }
12588  for(int x = 1; x < MaxRunningSpeedStr.Length() + 1; x++)
12589  {
12590  if((MaxRunningSpeedStr[x] < '0') || (MaxRunningSpeedStr[x] > '9'))
12591  {
12592  TimetableMessage(GiveMessages, "Train maximum running speed contains invalid characters in '" + TrainInfoStr + "'");
12593  Utilities->CallLogPop(893);
12594  return(false);
12595  }
12596  }
12597  MaxRunningSpeed = MaxRunningSpeedStr.ToInt();
12598  if(MaxRunningSpeed > TTrain::MaximumSpeedLimit) // 400kph = 250mph
12599  {
12600  MaxRunningSpeed = TTrain::MaximumSpeedLimit;
12601  if(!MRSHigh) // added at v2.4.0
12602  {
12603  TimetableMessage(GiveMessages, "Train maximum running speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
12604  MRSHigh = true;
12605  }
12606  }
12607  if(MaxRunningSpeed < 10)
12608  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12609  {
12610  MaxRunningSpeed = 10;
12611  if(!MRSLow) // added at v2.4.0
12612  {
12613  TimetableMessage(GiveMessages, "Train maximum running speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
12614  MRSLow = true;
12615  }
12616  }
12617  Pos = Remainder.Pos(';'); // 5th delimiter
12618  AnsiString MassStr = Remainder.SubString(1, Pos - 1);
12619 
12620  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12621  if(MassStr == "")
12622  {
12623  TimetableMessage(GiveMessages, "Train mass missing in '" + TrainInfoStr + "'");
12624  Utilities->CallLogPop(895);
12625  return(false);
12626  }
12627  for(int x = 1; x < MassStr.Length() + 1; x++)
12628  {
12629  if((MassStr[x] < '0') || (MassStr[x] > '9'))
12630  {
12631  TimetableMessage(GiveMessages, "Train mass contains invalid characters in '" + TrainInfoStr + "'");
12632  Utilities->CallLogPop(896);
12633  return(false);
12634  }
12635  }
12636  Mass = MassStr.ToInt() * 1000; // convert tonnes to kg
12637  if(Mass > TTrain::MaximumMassLimit) // 10,000tonnes
12638  {
12639  Mass = TTrain::MaximumMassLimit;
12640  if(!MassHigh) // added at v2.4.0
12641  {
12642  TimetableMessage(GiveMessages, "Train mass > 10,000 tonnes in '" + TrainInfoStr + "'. Setting it to 10,000 tonnes");
12643  MassHigh = true;
12644  }
12645  }
12646  if(Mass == 0)
12647  {
12648  TimetableMessage(GiveMessages, "Train mass zero in '" + TrainInfoStr + "'");
12649  Utilities->CallLogPop(897);
12650  return(false);
12651  }
12652  Pos = Remainder.Pos(';'); // 6th delimiter
12653  AnsiString MaxBrakeForceStr = Remainder.SubString(1, Pos - 1);
12654 
12655  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12656  if(MaxBrakeForceStr == "")
12657  {
12658  TimetableMessage(GiveMessages, "Train braking force missing in '" + TrainInfoStr + "'");
12659  Utilities->CallLogPop(898);
12660  return(false);
12661  }
12662  for(int x = 1; x < (MaxBrakeForceStr.Length() + 1); x++)
12663  {
12664  if((MaxBrakeForceStr[x] != '.') && ((MaxBrakeForceStr[x] < '0') || (MaxBrakeForceStr[x] > '9')))
12665  {
12666  TimetableMessage(GiveMessages, "Train braking force contains invalid characters in '" + TrainInfoStr + "'");
12667  Utilities->CallLogPop(899);
12668  return(false);
12669  }
12670  }
12671  double MaxBrakeForce = MaxBrakeForceStr.ToDouble() * 1000;
12672 
12673  // convert to kg force
12674  if((MaxBrakeForce / Mass) > 1) // gives 'g' braking - 9.81m/s/s
12675  {
12676  MaxBrakeForce = Mass;
12677  if(!BFHigh) // added at v2.4.0
12678  {
12679  TimetableMessage(GiveMessages, "Train braking force too high in '" + TrainInfoStr + "'. Setting it to the same as the train mass");
12680  BFHigh = true;
12681  }
12682  }
12683  if((MaxBrakeForce / Mass) < 0.01)
12684  {
12685  MaxBrakeForce = Mass * 0.01;
12686  if(!BFLow) // added at v2.4.0
12687  {
12688  TimetableMessage(GiveMessages, "Train braking force too low in '" + TrainInfoStr + "'. Setting it to 1% of the train mass");
12689  BFLow = true;
12690  }
12691  }
12692  // convert to m/s/s
12693  MaxBrakeRate = MaxBrakeForce / Mass * 9.81;
12694  // now may have just a power entry or power and signaller max. speed
12695  AnsiString GrossPowerStr = "", SignallerSpeedStr = "";
12696 
12697  if(SemiColonCount == 6)
12698  {
12699  GrossPowerStr = Remainder;
12700  SignallerSpeedStr = "30"; // default value
12701  }
12702  else // must be 7
12703  {
12704  Pos = Remainder.Pos(';'); // 7th delimiter
12705  GrossPowerStr = Remainder.SubString(1, Pos - 1);
12706  SignallerSpeedStr = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12707  }
12708  // deal with GrossPower
12709  if(GrossPowerStr == "")
12710  {
12711  TimetableMessage(GiveMessages, "Train power missing in '" + TrainInfoStr + "'");
12712  Utilities->CallLogPop(901);
12713  return(false);
12714  }
12715  for(int x = 1; x < GrossPowerStr.Length() + 1; x++)
12716  {
12717  if((GrossPowerStr[x] < '0') || (GrossPowerStr[x] > '9'))
12718  {
12719  TimetableMessage(GiveMessages, "Train power contains invalid characters in '" + TrainInfoStr + "'");
12720  Utilities->CallLogPop(902);
12721  return(false);
12722  }
12723  }
12724 
12725  double GrossPower = GrossPowerStr.ToInt() * 1000; // convert to W
12726 
12727  if(GrossPower > TTrain::MaximumPowerLimit) // 100MW
12728  {
12729  GrossPower = TTrain::MaximumPowerLimit;
12730  if(!PwrHigh)
12731  {
12732  TimetableMessage(GiveMessages, "Train power > 100,000kW in '" + TrainInfoStr + "'. Setting it to 100,000kW");
12733  PwrHigh = true;
12734  }
12735  }
12736  else if(GrossPower == 0) // changed at v2.4.0
12737  {
12738  GrossPower = 0.1;
12739  // can't be zero or AValue is zero and then have divide by zero error, so set to 0.1W so acceleration tiny (though should be intercepted before accel calculated)
12740  }
12741  else if((GrossPower > 0) && (GrossPower < 10000))
12742  // added at v2.4.0 to ensure min power of 8kW at rail unless zero (otherwise could have too low AValues
12743  {
12744  GrossPower = 10000;
12745  }
12746  PowerAtRail = GrossPower * 0.8;
12747  // apply ratio of 80% for rail to gross power (seems about average from an internet search)
12748 
12749  // deal with SignallerSpeed
12750  if(SignallerSpeedStr == "")
12751  {
12752  TimetableMessage(GiveMessages, "Signaller speed not set in '" + TrainInfoStr + "', either set a value or remove the extra semicolon");
12753  Utilities->CallLogPop(1771);
12754  return(false);
12755  }
12756  for(int x = 1; x < SignallerSpeedStr.Length() + 1; x++)
12757  {
12758  if((SignallerSpeedStr[x] < '0') || (SignallerSpeedStr[x] > '9'))
12759  {
12760  TimetableMessage(GiveMessages, "Signaller speed contains invalid characters in '" + TrainInfoStr + "'");
12761  Utilities->CallLogPop(1769);
12762  return(false);
12763  }
12764  }
12765  SignallerSpeed = SignallerSpeedStr.ToInt();
12766  if(SignallerSpeed > TTrain::MaximumSpeedLimit)
12767  {
12768  SignallerSpeed = TTrain::MaximumSpeedLimit;
12769  if(!SigSHigh)
12770  {
12771  TimetableMessage(GiveMessages, "Signaller speed > 400km/h in '" + TrainInfoStr + "'. Setting it to 400km/h");
12772  SigSHigh = true;
12773  }
12774  }
12775  if(SignallerSpeed < 10)
12776  // changed at v0.6 to prevent low max speeds - can cause problems in SetTrainMovementValues
12777  {
12778  SignallerSpeed = 10;
12779  if(!SigSLow)
12780  {
12781  TimetableMessage(GiveMessages, "Signaller speed can't be less than 10km/h in '" + TrainInfoStr + "', it will be set to 10km/h");
12782  SigSLow = true;
12783  }
12784  // Utilities->CallLogPop(1770);
12785  // return false;
12786  }
12787  Utilities->CallLogPop(904);
12788  return(true);
12789 }
12790 
12791 // ---------------------------------------------------------------------------
12792 
12793 bool TTrainController::SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &NumberOfRepeats,
12794  bool GiveMessages)
12795 {
12796  // Format must be: R;mm;dd;nn mm may be 1, 2 or more digits, dd may be 1 or 2 digits, nn may be 1, 2 or more digits
12797  // function checks validity of each item and returns false for error
12798  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SplitRepeat," + OneEntry);
12799  if(OneEntry.Length() < 7)
12800  {
12801  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
12802  Utilities->CallLogPop(865);
12803  return(false);
12804  }
12805  int SemiColonCount = 0;
12806 
12807  for(int x = 1; x < OneEntry.Length() + 1; x++)
12808  {
12809  if(OneEntry[x] == ';')
12810  {
12811  SemiColonCount++;
12812  }
12813  }
12814  if(SemiColonCount != 3)
12815  {
12816  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
12817  Utilities->CallLogPop(866);
12818  return(false);
12819  }
12820  if((OneEntry[1] != 'R') || (OneEntry[2] != ';'))
12821  {
12822  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - should be 'R;m;d;n'");
12823  Utilities->CallLogPop(867);
12824  return(false);
12825  }
12826  AnsiString Remainder = OneEntry.SubString(3, OneEntry.Length() - 2);
12827  // strip off R;
12828 
12829  int Pos = 0;
12830 
12831  Pos = Remainder.Pos(';');
12832  AnsiString MinutesStr = Remainder.SubString(1, Pos - 1);
12833 
12834  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12835  if(MinutesStr == "")
12836  {
12837  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute increment segment missing");
12838  Utilities->CallLogPop(868);
12839  return(false);
12840  }
12841  if(MinutesStr.Length() > 3)
12842  // added for v2.3.1 following Albie Vowles' reported error in repeat value 03/02/20
12843  {
12844  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - minute value too high, maximum value is 999");
12845  Utilities->CallLogPop(2119);
12846  return(false);
12847  }
12848  for(int x = 1; x < MinutesStr.Length() + 1; x++)
12849  {
12850  if((MinutesStr[x] < '0') || (MinutesStr[x] > '9'))
12851  {
12852  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in minute increment segment");
12853  Utilities->CallLogPop(869);
12854  return(false);
12855  }
12856  }
12857  RearStartOrRepeatMins = MinutesStr.ToInt();
12858  if(RearStartOrRepeatMins == 0)
12859  {
12860  TimetableMessage(GiveMessages, "Repeat minute increment is zero in: '" + OneEntry + "' - can't have a zero value");
12861  Utilities->CallLogPop(870);
12862  return(false);
12863  }
12864  Pos = Remainder.Pos(';');
12865  AnsiString DigitsStr = Remainder.SubString(1, Pos - 1);
12866 
12867  Remainder = Remainder.SubString(Pos + 1, Remainder.Length() - Pos);
12868  if(DigitsStr == "")
12869  {
12870  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - headcode increment segment missing");
12871  Utilities->CallLogPop(871);
12872  return(false);
12873  }
12874  for(int x = 1; x < DigitsStr.Length() + 1; x++)
12875  {
12876  if((DigitsStr[x] < '0') || (DigitsStr[x] > '9'))
12877  {
12878  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in headcode increment segment");
12879  Utilities->CallLogPop(872);
12880  return(false);
12881  }
12882  }
12883  if(DigitsStr.Length() > 2)
12884  {
12885  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - maximum number of digits for headcode increment is 2");
12886  Utilities->CallLogPop(873);
12887  return(false);
12888  }
12889  FrontStartOrRepeatDigits = DigitsStr.ToInt();
12890 /* allow zero digit increments so HC can stay same for repeated services - for many suburban services the headcode digits relate to the
12891  route rather than the service
12892  if(FrontStartOrRepeatDigits == 0)
12893  {
12894  TimetableMessage(GiveMessages, "Repeat headcode increment is zero in: '" + OneEntry + "' - can't have a zero value");
12895  Utilities->CallLogPop(874);
12896  return false;
12897  }
12898 */
12899  if(!Last2CharactersBothDigits(0, ServiceReference) && (FrontStartOrRepeatDigits > 0))
12900  // new for v0.6b for unrestricted headcodes
12901  {
12902  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry +
12903  "' - a repeating service with incrementing digits must have digits as its last two headcode characters");
12904  Utilities->CallLogPop(1889);
12905  return(false);
12906  }
12907  AnsiString NumberStr = Remainder;
12908 
12909  if(NumberStr == "")
12910  {
12911  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - number of repeats missing");
12912  Utilities->CallLogPop(875);
12913  return(false);
12914  }
12915  if(NumberStr.Length() > 4)
12916  // added for v2.3.1 following Albie Vowles' reported error 03/02/20
12917  {
12918  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - repeat value too high, no timetabled event can exceed 95 hours & 59 minutes");
12919  Utilities->CallLogPop(2118);
12920  return(false);
12921  }
12922  for(int x = 1; x < NumberStr.Length() + 1; x++)
12923  {
12924  if((NumberStr[x] < '0') || (NumberStr[x] > '9'))
12925  // catches negative numbers
12926  {
12927  TimetableMessage(GiveMessages, "Error in repeat: '" + OneEntry + "' - non-digit character in number of repeats");
12928  Utilities->CallLogPop(876);
12929  return(false);
12930  }
12931  }
12932  NumberOfRepeats = NumberStr.ToInt();
12933  if(NumberOfRepeats == 0)
12934  {
12935  TimetableMessage(GiveMessages, "Number of repeats is zero in: '" + OneEntry + "' - if no repeats are needed the repeat should be omitted");
12936  Utilities->CallLogPop(877);
12937  return(false);
12938  }
12939  Utilities->CallLogPop(878);
12940  return(true);
12941 }
12942 
12943 // ---------------------------------------------------------------------------
12944 
12945 bool TTrainController::SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag) //TwoLocationFlag added at v2.9.1
12946 /* Note that here the TrainDataVector has been compiled with FinalCall true in ProcessOneTimetableLine so work on the
12947  vector rather than the timetable
12948  Note also that for unlocated Snt entries the LocationType hasn't yet been set
12949 
12950  Many of the errors caught here duplicate those in the preliminary checks, but leave in for completeness
12951 
12952 Updated significantly for v2.15.0. Current procedure:-
12953 
12954 Preliminary checks for v0.2b without changing anything, carry each out separately:-
12955  1) must have at least one actionvector entry
12956  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
12957  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
12958  4) first entry must be a start;
12959  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
12960  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
12961  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
12962  5) a start must be the first entry;
12963  6) a repeat entry must be the last;
12964  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
12965  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
12966  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
12967 
12968 Set location for located Snt or Snt-sh and ensure successor AtLocation
12969 For unlocated Snt-sh give error message
12970 For unlocated Snt & not sig control check successor moving
12971 
12972 Check all other starts (all located) have valid successors
12973 
12974 Set location for Sns-sh and Sns-fsh from following TimeLoc, if not one then give message
12975 
12976 Carry out linkage checks to ensure all links present, no data set yet & locations not checked yet. First check for duplicates, then for cross
12977 references, then for non-repeating shuttle cross refs. This is done because the later location naming functions give error messages if there
12978 are missing links.
12979 
12980 Set names for all Fns finishes from earlier named event or fail if can't find
12981 Set names for linked Sns events with same event times from above, but first carry out immediate successor checks and give error message for:-
12982 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
12983 not found, that check done later.
12984 
12985 Trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set
12986 from linked fsp/rsp events)
12987 
12988 Name all fsp/rsp events, then check that all named or give error message.
12989 
12990 Set all Sfs names from above fsp/rsp links with same event times, but first carry out immediate successor checks and give error message for:-
12991 no successors, moving successor, another start sequence, a finish that isn't Frh or Fjo or a repeat. No error messages given here for location
12992 not found, that check done later.
12993 
12994 Set remaining AtLoc Command locations from preceding named event
12995 
12996 All location names should now be set
12997 
12998 Final detailed check of names for all AtLoc Commands. If find any without a name give an error message:-
12999 If jbo, fsp, rsp, cdt or dsc say must be preceded by a named event at same location, normally an arrival
13000 If Sns or Sfs say to make sure the linked finish event is preceded by a named event at same location, normally an arrival
13001 If Snt-sh say to make sure that the service starts with zero speed and is at a named location
13002 If Sns-fsh or Sns-sh say to make sure that the event is followed (not necessarily immediately) by a departure
13003 If Frh, Fns, Fjo, Frh-sh, Fns-sh or F-nshs say that the event must be preceded by an event at the same location that has an identified location name,
13004 normally an arrival.
13005 Missing: pas & Fer not AtLoc, Snt whether located or not covered in detail earlier.
13006 
13007 Later checks as before 2.15.0 changes:-
13008 
13009 Check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13010 
13011 Set arrival & departure times for TimeLocs & set their EventTimes to -1 (up to now all have times as EventTime)
13012 
13013 Perform remaining successor checks for TimeLocs
13014 
13015 Check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1, & repeats have no times
13016 set
13017 
13018 Check times stay same or increase through a service, note that can have time of 0 if include midnight
13019 
13020 Check locations consistent
13021 
13022 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0) i.e.
13023 same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0. Message
13024 given in InterfaceUnit
13025 
13026 Check all locations except unlocated 'Snt' & 'Fer' have LocationName set and throw error if not.
13027 
13028 Carry out full cross reference and duplicate link checks for all services inc shuttles, and set data and check location consistency
13029 
13030 Check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
13031 when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles, don't ever need to and as
13032 designed would skip repeats
13033 
13034 Check all entries have all types set to something and throw error if not
13035 
13036 All OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
13037 
13038 Check that don't include any Continuation names
13039 
13040 Check that all repeat times below 96h
13041 
13042 Now that all set up change any extended headcodes back to ordinary headcodes (had been service references until now.
13043 
13044 Finally call BuildContinuationTrainExpectationMultiMap
13045 
13046 ***********************************
13047 
13048 For info:-
13049 class TActionVectorEntry //contains a single train action - repeat entry is also of this class though no train action is taken for it
13050 {
13051 public:
13052 AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode; ///< string values for timetabled event entries, null
13054 bool SignallerControl; ///< indicates a train that is defined by the timetable as under signaller control
13055 bool Warning; ///< if set triggers an alert in the warning panel when the action is reached
13056 int NumberOfRepeats; ///< the number of repeating services
13057 int RearStartOrRepeatMins, FrontStartOrRepeatDigits; ///< dual-purpose variables used for the TrackVectorPositions of the rear and front
13059 TDateTime EventTime, ArrivalTime, DepartureTime; ///< relevant times at which the action is timetabled, zeroed on creation so change
13061 TNumList ExitList; ///< the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
13062 TTimetableFormatType FormatType; ///< defines the timetable action type
13063 TTimetableLocationType LocationType; ///< indicates where the train is when the relevant action occurs
13064 TTimetableSequenceType SequenceType; ///< indicates where in the sequence of codes the action lies
13065 TTimetableShuttleLinkType ShuttleLinkType; ///< indicates whether or not the action relates to a shuttle service link
13066 TTrainDataEntry *LinkedTrainEntryPtr; ///< link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle
13068 TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr; ///< pointer used by shuttles for the non-shuttle train links, in & out, the
13070 
13071 // inline function
13072 
13074 TActionVectorEntry() {
13075 RearStartOrRepeatMins=0; FrontStartOrRepeatDigits=0; NumberOfRepeats=0; FormatType=NoFormat;
13076 SequenceType=NoSequence; LocationType=NoLocation; ShuttleLinkType=NoShuttleLink, EventTime=TDateTime(-1);
13077 ArrivalTime=TDateTime(-1); DepartureTime=TDateTime(-1); LinkedTrainEntryPtr=0; NonRepeatingShuttleLinkEntryPtr=0;
13078 Warning = false; SignallerControl = false;
13079 }
13080 };
13081 
13082 typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
13083 
13084 class TTrainDataEntry //contains all data for a single train - copied into train object when becomes active
13085 {
13086 public:
13087 AnsiString HeadCode, ServiceReference, Description; ///< headcode is the first train's headcode, rest are calculated from repeat
13090 double MaxBrakeRate; ///< in metres/sec/sec
13091 double MaxRunningSpeed; ///< in km/h
13092 double PowerAtRail; ///< in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
13093 int Mass; ///< in kg
13094 int NumberOfTrains; ///< number of repeats + 1
13095 int SignallerSpeed; ///< in km/h for use when under signaller control
13096 int StartSpeed; ///< in km/h
13097 TActionVector ActionVector; ///< all the actions for the train
13098 TTrainOperatingDataVector TrainOperatingDataVector; ///< operating information for the train including all its repeats
13099 
13100 //inline function
13101 
13103 TTrainDataEntry()
13104 {
13105 StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;
13106 }
13107 };
13108 
13109 Allowable successors:-
13110 Snt unlocated -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas; No others
13111 Snt located -> No starts, no finishes except Frh, Fjo (as of v2.0.0), Fns, and F-nshs, no repeat, pas, TimeTimeLoc or TimeLoc arr;
13112 any other cmd or TimeLoc (dep) OK
13113 Snt-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK
13114 Sfs -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc, TimeLoc arr, rsp, fsp; any other cmd or
13115 TimeLoc (dep) OK [must have departure & arrival before another split]
13116 Sns -> No starts, finishes except Frh & Fjo (as of v2.15.0), repeats, pas, TimeTimeLoc or TimeLoc arr; any other cmd or TimeLoc (dep) OK
13117 Sns-sh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13118 sequence to
13119 set location, else fails)
13120 Sns-fsh -> No starts, finishes, repeats, pas or TimeTimeLoc; any other cmd or TimeLoc (dep) OK (must have a TimeLoc departure somewhere in
13121 sequence to
13122 set location, else fails)
13123 Fns -> R only [must be preceded by a TimeLoc arrival at the finish location, not necessarily immediately]
13124 F-nshs -> Nothing (no repeats permitted)
13125 Fjo -> R only
13126 Frh -> R only
13127 Fer -> R only
13128 Frh-sh -> R only
13129 Fns-sh -> R only
13130 jbo -> No starts, repeats, pas, Fer or TimeTimeLoc; TimeLoc (dep), others OK [must be preceded by an event whose location is set]
13131 fsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13132 rsp -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13133 cdt -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13134 dsc -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK [must be preceded by an event whose location is set]
13135 TimeLoc (arr) -> No starts, repeats, Fer, pas or TimeTimeLoc; TimeLoc (dep) or any other OK
13136 TimeLoc (dep) -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13137 TimeTimeLoc -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13138 (new) pas -> Fer, TimeLoc (arr), or TimeTimeLoc, (new) pas OK, no others
13139 Repeat -> Nothing
13140 
13141 There must be a TimeLoc arrival (or a Sns start at location) in a sequence so successive cmd locations can be set
13142 Check all Snt's & set Locations if located (located = zero start speed, either element at a location (but if rear element
13143 is a continuation then treated as unlocated), and location listed in the next TimeLoc entry, though needn't be immediately after)
13144 If Snt entry at a location specified in a following TimeLoc entry but start speed > 0 give error message
13145 Check all times increase or stay same through ActionVector
13146 Cycle through all entries in vector setting arr & dep times based on above list
13147 Add locations to all relevant cmd entries based on earlier arrival location (or earlier reference for Sfs & Sns)
13148 Check locations match the arr & dep TimeLoc entries
13149 Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs
13150 Make above valid succession checks
13151 Check all splits have matching Sfs headcodes (both ways), add locations to Sfs's & check times same [Sfs loc derived from preceding fsp/rsp
13152 loc]
13153 Check all new service headcodes (Sns) have matching headcodes (both ways), add locations to Sns's & check times same [Sns loc derived from
13154 preceding Fns loc]
13155 Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
13156 Check each Fns has matching Sns headcodes (both ways), add locations to Fns's & check times same
13157 Check all joins have matching headcodes (both ways), locations & times & don't occur in same sequence
13158 Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
13159 Set train info for Sfs & Sns entries
13160 Check each repeat entry exactly matches any included joins or splits (user has to enter it to show that really wants it)
13161 Check at least one platform long enough for a split (only need 2 lengths) & disallow if not, need length of 2 & 1 extra
13162 element at each end, or length of 3 & 1 extra element at either end
13163 Check all TimeLocs have either Arr or Dep times set and EventTime == -1
13164 Check all Cmds have EventTime set & Arr & Dep times = -1
13165 Check all locations except unlocated Snts, Fers and Repeats have a LocationName
13166 
13167 Give messages in function if errors detected and clear the vector. Return false for failure.
13168 */
13169 
13170 /* Earlier checks:-
13171 Checks carried out with error messages in this function:-
13172 At least one comma in the line (it's based on a csv file);
13173 No entries following train information;
13174 At least one comma in remainder after train information (i.e at least a start and a finish entry);
13175 SplitEntry returns false in an intermediate entry - message repeats the entry for information;
13176 First entry not a start entry;
13177 Train information incomplete before a start entry;
13178 Entry follows a finish entry but doesn't begin with 'R';
13179 SplitEntry returns false in a finish entry - message repeats the entry for information;
13180 Last action entry isn't a finish entry.
13181 
13182 Function returns false with no message if:-
13183 Timetable start time invalid (no message is given for an invalid time as the line is assumed to be an irrelevant line; if no start
13184 time is found at all then an error message is given in the calling function);
13185 SplitTrainInfo returns false (message given in called function);
13186 SplitRepeat returns false (message given in called function).
13187 
13188 Double crosslink (shuttle) table: [OtherHeadCode, NonRepeatingShuttleLinkHeadCode, LinkedTrainEntryPtr, NonRepeatingShuttleLinkEntryPtr] <-- these for
13189 easier searching for this table
13190 
13191 Command Format OtherHead NonRepeating- LinkedTrain- NonRepeating- Decsription
13192  Code ShuttleLink- EntryPtr ShuttleLink
13193  HeadCode EntryPtr
13194 
13195 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
13196 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
13197 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
13198 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (fdr-shld be rtn)N (shld be fdr) Luckily NonRep link not needed
13199 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
13200 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
13201 
13202 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
13203 
13204 */
13205 {
13206  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SecondPassActions,");
13207  if(TrainDataVector.empty())
13208  {
13209  SecondPassMessage(GiveMessages, "Error in timetable - there appear to be no train services in the timetable, it must contain at least one");
13210  TrainDataVector.clear();
13211  Utilities->CallLogPop(1832);
13212  return(false);
13213  }
13214 /* new preliminary checks for v0.2b without changing anything, carry each out separately:-
13215  1) must have at least one actionvector entry
13216  2) if first actionvector entry not SignallerControl then must have at least one more actionvector entry
13217  3) if first actionvector entry is SignallerControl then must have no more actionvector entries except a repeat
13218  4) first entry must be a start;
13219  4a) if first entry is Snt and second is a finish then it can't be Fns-sh or Frh-sh
13220  4b) if first entry is Sns or Sfs and second is a finish then it must be either Frh or Fjo
13221  4c) if first entry is Snt-sh, Sns-sh or Sns-fsh second can't be a finish
13222  5) a start must be the first entry;
13223  6) a repeat entry must be the last;
13224  7) for other than SignallerControl the last entry must be repeat or finish; if last entry is a repeat the last but one must be a finish;
13225  8) a finish entry must be the last or last but one, and if last but one the last must be a repeat
13226  Other successor errors will be caught later as all 'throws' changed to messages prior to the bulk of the sucessor checks
13227 */
13228 
13229  TwoLocationList.clear(); //empty the list to begin with, added at v2.9.1
13230  TwoLocationFlag = false; //added at v2.9.1
13231  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (1)
13232  {
13233  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13234  if(TrainDataVector.at(x).ActionVector.empty())
13235  {
13236  SecondPassMessage(GiveMessages, "Error in timetable - the following service has no listed events, there must be at least one: " + TDEntry.HeadCode);
13237  TrainDataVector.clear();
13238  Utilities->CallLogPop(1833);
13239  return(false);
13240  }
13241  }
13242  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (2)
13243  {
13244  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13245  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13246  if(!(AVEntry0.SignallerControl))
13247  {
13248  if(TrainDataVector.at(x).ActionVector.size() == 1)
13249  {
13250  SecondPassMessage(GiveMessages, "Error in timetable - service must have a start event and at least one other for: " + TDEntry.HeadCode);
13251  TrainDataVector.clear();
13252  Utilities->CallLogPop(1822);
13253  return(false);
13254  }
13255  }
13256  }
13257  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (3)
13258  {
13259  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13260  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13261  if(AVEntry0.SignallerControl)
13262  {
13263  if(TrainDataVector.at(x).ActionVector.size() > 2)
13264  {
13265  SecondPassMessage(GiveMessages,
13266  "Error in timetable - a signaller controlled service can have no more than one item (a repeat) after the start event, see: " +
13267  TDEntry.HeadCode);
13268  TrainDataVector.clear();
13269  Utilities->CallLogPop(1837);
13270  return(false);
13271  }
13272  if(TrainDataVector.at(x).ActionVector.size() > 1)
13273  {
13274  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13275  if(AVEntry1.FormatType != Repeat)
13276  {
13277  SecondPassMessage(GiveMessages,
13278  "Error in timetable - a signaller controlled service cannot have any other than a repeat after the start event, see: " + TDEntry.HeadCode);
13279  TrainDataVector.clear();
13280  Utilities->CallLogPop(1838);
13281  return(false);
13282  }
13283  }
13284  }
13285  }
13286  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (4)
13287  {
13288  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13289  TActionVectorEntry AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13290  if(AVEntry0.SequenceType != StartSequence)
13291  {
13292  SecondPassMessage(GiveMessages, "Error in timetable - the first event must be a start for: " + TDEntry.HeadCode);
13293  TrainDataVector.clear();
13294  Utilities->CallLogPop(1824);
13295  return(false);
13296  }
13297  if((AVEntry0.Command == "Snt") && !AVEntry0.SignallerControl) // (4a) sig control condition added so there is a second AVEntry
13298  // 4a added at v2.0.0. This is only a rough check, Fer only valid for an unlocated Snt
13299  // and others for a located Snt, but those checks done later
13300  {
13301  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1); // must be a second entry if first not signallercontrol
13302  if((AVEntry1.SequenceType == FinishSequence) && ((AVEntry1.Command == "Fns-sh") || (AVEntry1.Command == "Frh-sh")))
13303  {
13304  SecondPassMessage(GiveMessages, "Error in timetable - finish events Fns-sh and Frh-sh not permitted immediately after an Snt entry for: " +
13305  TDEntry.HeadCode); //these are the only AtLoc finishes not allowed
13306  TrainDataVector.clear();
13307  Utilities->CallLogPop(2046);
13308  return(false);
13309  }
13310  }
13311  if((AVEntry0.Command == "Sns") || (AVEntry0.Command == "Sfs")) // (4b)
13312  // 4b added at v2.15.0
13313  {
13314  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13315  if((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo"))
13316  {
13317  SecondPassMessage(GiveMessages, "Error in timetable - only 'Frh' or 'Fjo' finish events are permitted immediately after "
13318  "an 'Sns' or 'Sfs' event for: " + TDEntry.HeadCode + ". The program is unable to determine the "
13319  "location of any other type of finish.");
13320  TrainDataVector.clear();
13321  Utilities->CallLogPop(2580);
13322  return(false);
13323  }
13324  }
13325  if((AVEntry0.Command == "Snt-sh") || (AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh")) // (4c)
13326  // 4c added at v2.15.0
13327  {
13328  TActionVectorEntry AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13329  if(AVEntry1.SequenceType == FinishSequence)
13330  {
13331  SecondPassMessage(GiveMessages, "Error in timetable - a finish event can't immediately follow an 'Snt-sh', 'Sns-sh' or 'Sns-fsh' "
13332  "event for: " + TDEntry.HeadCode);
13333  TrainDataVector.clear();
13334  Utilities->CallLogPop(2616);
13335  return(false);
13336  }
13337  }
13338  }
13339  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (5)
13340  {
13341  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13342  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13343  {
13344  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13345  if((AVEntry.SequenceType == StartSequence) && (y != 0))
13346  {
13347  SecondPassMessage(GiveMessages, "Error in timetable - a start event is present that is not the first event for: " + TDEntry.HeadCode);
13348  TrainDataVector.clear();
13349  Utilities->CallLogPop(1825);
13350  return(false);
13351  }
13352  }
13353  }
13354  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (6)
13355  {
13356  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13357  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13358  {
13359  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13360  if((AVEntry.FormatType == Repeat) && (y != (TrainDataVector.at(x).ActionVector.size() - 1)))
13361  {
13362  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is present that is not the last item for: " + TDEntry.HeadCode);
13363  TrainDataVector.clear();
13364  Utilities->CallLogPop(1826);
13365  return(false);
13366  }
13367  }
13368  }
13369  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (7)
13370  {
13371  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13372  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13373  {
13374  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13375  if((y == 0) && AVEntry.SignallerControl)
13376  {
13377  break;
13378  }
13379  if(y == (TrainDataVector.at(x).ActionVector.size() - 1))
13380  {
13381  if((AVEntry.FormatType != Repeat) && (AVEntry.SequenceType != FinishSequence))
13382  {
13383  SecondPassMessage(GiveMessages, "Error in timetable - the last item must be either a finish event or a repeat for: " + TDEntry.HeadCode);
13384  TrainDataVector.clear();
13385  Utilities->CallLogPop(1827);
13386  return(false);
13387  }
13388  if(AVEntry.FormatType == Repeat)
13389  {
13390  const TActionVectorEntry &LastButOneAVEntry = TrainDataVector.at(x).ActionVector.at(y - 1);
13391  if(LastButOneAVEntry.SequenceType != FinishSequence)
13392  {
13393  SecondPassMessage(GiveMessages, "Error in timetable - the event immediately before the repeat must be a finish for: " + TDEntry.HeadCode);
13394  TrainDataVector.clear();
13395  Utilities->CallLogPop(1828);
13396  return(false);
13397  }
13398  }
13399  }
13400  }
13401  }
13402  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // (8)
13403  {
13404  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13405  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13406  {
13407  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13408  if(AVEntry.SequenceType == FinishSequence)
13409  {
13410  if((y != (TrainDataVector.at(x).ActionVector.size() - 1)) && (y != (TrainDataVector.at(x).ActionVector.size() - 2)))
13411  {
13412  SecondPassMessage(GiveMessages, "Error in timetable - a finish event must be either the last or last but one for: " + TDEntry.HeadCode);
13413  TrainDataVector.clear();
13414  Utilities->CallLogPop(1829);
13415  return(false);
13416  }
13417  if(y == (TrainDataVector.at(x).ActionVector.size() - 2))
13418  {
13419  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
13420  {
13421  SecondPassMessage(GiveMessages, "Error in timetable - the only event that can follow a finish event is a repeat for: " + TDEntry.HeadCode);
13422  TrainDataVector.clear();
13423  Utilities->CallLogPop(1830);
13424  return(false);
13425  }
13426  }
13427  }
13428  }
13429  }
13430 
13431  // end of new preliminary checks
13432 
13433  // check start event successor validity
13434  // For Snt & Snt-sh set location if stopped, don't set any times yet
13435  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13436  {
13437  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13438  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13439  // use reference so can change internals where necessary
13440  if((AVEntry0.Command == "Snt") || (AVEntry0.Command == "Snt-sh"))
13441  {
13442  AnsiString LocationName = "";
13443  if(IsSNTEntryLocated(0, TDEntry, LocationName))
13444  // it is at a location
13445  {
13446  AVEntry0.LocationName = LocationName; //located Snt location name set
13447  AVEntry0.LocationType = AtLocation;
13448  // check successor validity for located Snt that isn't a SignallerControl entry
13449  if(!AVEntry0.SignallerControl)
13450  {
13451  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13452  // at least 2 entries present checked in integrity check so (1) valid
13453  if(!AtLocSuccessor(AVEntry1))
13454  {
13455  // Frh following Snt-sh will return false in location check, so no need to check here
13456  SecondPassMessage(GiveMessages, "Error in timetable - stopped 'Snt' or 'Snt-sh' followed by an illegal event for: " +
13457  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
13458  TrainDataVector.clear();
13459  Utilities->CallLogPop(523);
13460  return(false);
13461  }
13462  }
13463  }
13464  else // check not Snt-sh & carry out successor validity checks for unlocated Snt that isn't a SignallerControl entry
13465  {
13466  if(AVEntry0.Command == "Snt-sh")
13467  {
13468  SecondPassMessage(GiveMessages, "Error in timetable - 'Snt-sh' event not at stop location for: " + TDEntry.HeadCode);
13469  TrainDataVector.clear();
13470  Utilities->CallLogPop(1042);
13471  return(false);
13472  }
13473  AVEntry0.LocationType = EnRoute;
13474  if(!AVEntry0.SignallerControl)
13475  {
13476  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13477  // at least 2 entries checked in integrity check so (1) valid
13478  if(!MovingSuccessor(AVEntry1))
13479  {
13480  SecondPassMessage(GiveMessages, "Error in timetable - unlocated 'Snt' not followed by 'Fer', 'pas' or an arrival for: " +
13481  TDEntry.HeadCode);
13482  TrainDataVector.clear();
13483  Utilities->CallLogPop(790);
13484  return(false);
13485  }
13486  }
13487  }
13488  }
13489  // check other start successors, all AtLoc
13490  else if(AVEntry0.SequenceType == StartSequence)
13491  {
13492  const TActionVectorEntry &AVEntry1 = TrainDataVector.at(x).ActionVector.at(1);
13493  // at least 2 entries present checked in integrity check so (1) valid
13494  if(!AtLocSuccessor(AVEntry1))
13495  {
13496  SecondPassMessage(GiveMessages, "Error in timetable - 'Sfs', 'Sns', 'Sns-sh', 'Snt-fsh' or 'Sns-fsh' followed by an illegal event for: " +
13497  TDEntry.HeadCode + ". The event isn't valid for a stationary train.");
13498  TrainDataVector.clear();
13499  Utilities->CallLogPop(793);
13500  return(false);
13501  }
13502  }
13503  }
13504 
13505 
13506  // set Sns-sh & Sns-fsh locations same as following TimeLoc departure entry location, if no departure before end of sequence give error message
13507  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
13508  {
13509  bool FoundFlag = false;
13510  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13511  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13512  // use reference so can change internals
13513  if((AVEntry0.Command == "Sns-sh") || (AVEntry0.Command == "Sns-fsh"))
13514  {
13515  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
13516  {
13517  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y);
13518  if(AVEntry2.FormatType == TimeLoc)
13519  {
13520  FoundFlag = true;
13521  AVEntry0.LocationName = AVEntry2.LocationName; //Sns-sh & Sns-fsh location names set
13522  break;
13523  }
13524  }
13525  if(!FoundFlag)
13526  {
13527  SecondPassMessage(GiveMessages, "Error in timetable - no location departure following an 'Sns-sh' or 'Sns-fsh' event for: " + TDEntry.HeadCode);
13528  TrainDataVector.clear();
13529  Utilities->CallLogPop(851);
13530  return(false);
13531  }
13532  }
13533  }
13534 
13535 //carry out preliminary check on service ref linkages without setting any data - added at v2.15.0 as can be location errors if linked trains not present
13536 //first check for duplicates then linkages (also checked later but leave that in)
13537  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13538  {
13539  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13540  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13541  {
13542  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13543  if(AVEntry.OtherHeadCode != "")
13544  {
13545  if(!CheckForDuplicateCrossReferences(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
13546  {
13547  Utilities->CallLogPop(2610);
13548  return(false); // error message given in called function
13549  }
13550  }
13551  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
13552  {
13553  if(!CheckForDuplicateCrossReferences(3, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
13554  {
13555  Utilities->CallLogPop(2611);
13556  return(false); // error message given in called function
13557  }
13558  }
13559  }
13560  }
13561 //cross reference check
13562  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13563  {
13564  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13565  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13566  {
13567  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13568  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
13569  {
13570  if(AVEntry.OtherHeadCode != "")
13571  {
13572  if(!CheckCrossReferencesAndSetData(2, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, false, GiveMessages))
13573  // false = non-shuttle
13574  {
13575  Utilities->CallLogPop(2612);
13576  return(false); // error message given in called function
13577  }
13578  }
13579  }
13580  }
13581  }
13582 
13583 // now repeat the check just for the shuttles
13584  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13585  {
13586  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13587  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13588  {
13589  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13590  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
13591  {
13592  if(AVEntry.OtherHeadCode != "")
13593  {
13594  if(!CheckCrossReferencesAndSetData(3, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, false, GiveMessages))
13595  // true = shuttle
13596  {
13597  Utilities->CallLogPop(2613);
13598  return(false); // error message given in called function
13599  }
13600  }
13601  }
13602  }
13603  }
13604 
13605 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
13606  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13607  {
13608  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13609  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13610  {
13611  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13612  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
13613  {
13614  if(!CheckNonRepeatingShuttleLinksAndSetData(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, false, GiveMessages))
13615  {
13616  Utilities->CallLogPop(2614);
13617  return(false); // error message given in called function
13618  }
13619  }
13620  }
13621  }
13622 
13623 //at v2.15.0 we want to set Sns, Sfs location names, but to set Sns & Sfs first need the linked Fns & fsp/rsp to have locations set as they aren't yet,
13624 //and before v2.15.0 they were set from the corresponding Sns & Sfs locations, which in turn were set from later TimeLoc departures. At v2.15.0 it
13625 //is required to have these commands followed by Frh & Fjo, so this is why we need the linked Fns & fsp/rsp to have locations set first. Now all Fns
13626 //will have a TimeLoc before, so that can provide its location, but fsp/rsp? Must they have a TimeLoc before? No, and can't rely on starting Sfs
13627 //having the location set yet. So, new restriction, insist on an rsp/fsp having a TimeLoc before it or a located Snt, and use one of those to set the
13628 //location for the rsp/fsp and hence the linked Sfs.
13629 
13630 //NB can't allow an Sfs to be followed by another split or won't find a name, test with many existing tts then add an error to find it
13631 //Fns must be preceded by an arrival
13632 
13633 //set name for Fns from earlier location name or fail if can't find
13634  bool LocFoundFlag, FnsFoundFlag;
13635  TActionVectorEntry *AVEntryFns;
13636  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13637  {
13638  LocFoundFlag = false;
13639  FnsFoundFlag = false;
13640  for(int y = TrainDataVector.at(x).ActionVector.size() - 1; y >= 0; y--) //search backwards
13641  {
13642  if(TrainDataVector.at(x).ActionVector.at(y).Command == "Fns")
13643  {
13644  AVEntryFns = &TrainDataVector.at(x).ActionVector.at(y);
13645  FnsFoundFlag = true;
13646  continue;
13647  }
13648  if(!FnsFoundFlag)
13649  {
13650  continue;
13651  }
13652  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "")
13653  {
13654  LocFoundFlag = true;
13655  AVEntryFns->LocationName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
13656 // double EVT = double(AVEntryFns->EventTime); //test
13657  break; //name found
13658  }
13659  if(TrainDataVector.at(x).ActionVector.at(y).LocationType == AtLocation) //not named yet
13660  {
13661  continue;
13662  }
13663  else
13664  {
13665  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
13666  "by an event at the same location that has an identified location name, normally an arrival, see "
13667  + TrainDataVector.at(x).ServiceReference);
13668  TrainDataVector.clear();
13669  Utilities->CallLogPop(2596);
13670  return(false);
13671  }
13672  }
13673  if(FnsFoundFlag && !LocFoundFlag)
13674  {
13675  SecondPassMessage(GiveMessages, "Error in timetable - the program can't determine the location of an 'Fns' finish, it must be preceded "
13676  "by an event at the same location that has an identified location name, normally an arrival, see "
13677  + TrainDataVector.at(x).ServiceReference);
13678  TrainDataVector.clear();
13679  Utilities->CallLogPop(2597);
13680  return(false);
13681  }
13682  }
13683 
13684 //now set all names for Sns entries from the above, new at v2.15.0
13685  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13686  {
13687  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13688  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13689  // use reference so can change internals
13690  if(AVEntry0.Command == "Sns")
13691  {
13692  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called).
13693  //This isn't rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
13694  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
13695  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until
13696  //StripExcessFromHeadCode called at end of this function
13697  //need to be the same: forward & reverse service refs, event times, commands correspond
13698 
13699  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
13700  if(TDEntry.ActionVector.size() < 2)
13701  {
13702  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sns' event for: " + TDEntry.HeadCode);
13703  TrainDataVector.clear();
13704  Utilities->CallLogPop(2598);
13705  return(false);
13706  }
13707  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
13708  if(!AtLocSuccessor(AVEntry1))
13709  {
13710  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode +
13711  ". The event isn't valid for a stationary train.");
13712  TrainDataVector.clear();
13713  Utilities->CallLogPop(2599);
13714  return(false);
13715  }
13716  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
13717  (AVEntry1.FormatType == Repeat))
13718  {
13719  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sns' event is followed by an illegal event for: " + TDEntry.HeadCode);
13720  TrainDataVector.clear();
13721  Utilities->CallLogPop(2600);
13722  return(false);
13723  }
13724 
13725  //now set the location and location type
13726  TDateTime SnsEventTime = AVEntry0.EventTime;
13727 // double EVT = double(SnsEventTime); //test
13728  AnsiString SnsServiceRef = TDEntry.ServiceReference;
13729  bool BreakFlag = false;
13730  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
13731  {
13732  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
13733  {
13734  if((TrainDataVector.at(y).ActionVector.at(z).Command == "Fns") && (SnsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) &&
13735  (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
13736  { //forward linkage found
13737  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SnsServiceRef) //OtherHeadCode values are service refs, see above
13738  { //reverse linkage found
13739  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
13740  AVEntry0.LocationType = AtLocation;
13741  BreakFlag = true;
13742  break;
13743  }
13744  }
13745  }
13746  if(BreakFlag)
13747  {
13748  break;
13749  }
13750  }
13751  //test for any unnamed AtLoc entries at end of name setting
13752  }
13753  }
13754 
13755 //trap errors where rsp/fsp follows an Sfs without a TimeLoc arrival before (or unlikely to be able to set fsp/rsp/Sfs location because Sfs locs set from linked fsp/rsp events)
13756  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13757  {
13758  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13759  if(AVEntry0.Command == "Sfs")
13760  {
13761  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size(); y++)
13762  {
13763  if(TrainDataVector.at(x).ActionVector.at(y).LocationName != "") //must be a timeloc as only they have loc set and are AtLoc (non-AtLoc trapped above)
13764  {
13765  break;
13766  }
13767  else if((TrainDataVector.at(x).ActionVector.at(y).Command == "fsp") || (TrainDataVector.at(x).ActionVector.at(y).Command == "rsp"))
13768  {
13769  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event must be followed by a departure and arrival before another split, see " + TrainDataVector.at(x).ServiceReference);
13770  TrainDataVector.clear();
13771  Utilities->CallLogPop(2586);
13772  return(false);
13773  }
13774  }
13775  }
13776  }
13777 
13778 //now name fsp/rsp actions
13779  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13780  {
13781  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13782  {
13783  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13784  if(AVEntry.LocationName != "")
13785  {
13786  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
13787  {
13788  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
13789  // use reference so can change internals where necessary
13790  if((AVEntry2.Command == "fsp") || (AVEntry2.Command == "rsp"))
13791  {
13792  AVEntry2.LocationName = AVEntry.LocationName;
13793  } //test for any unnamed AtLoc entries at end of name setting
13794  else if(AVEntry2.LocationType != AtLocation)
13795  {
13796  break;
13797  }
13798  }
13799  }
13800  }
13801  }
13802 
13803 //check that all named or give error message
13804  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13805  {
13806  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13807  {
13808  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13809  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
13810  {
13811  if(AVEntry.LocationName == "")
13812  {
13813  SecondPassMessage(GiveMessages, "Error in timetable - an 'fsp' or 'rsp' event must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
13814  TrainDataVector.clear();
13815  Utilities->CallLogPop(2617);
13816  return(false);
13817  }
13818  }
13819  }
13820  }
13821 
13822 //now set all Sfs entries from the above
13823  for(unsigned int x = 0; x < TrainDataVector.size(); x++) //at v2.15.0 set Sfs & Sns locations from corresponding fsp/rsp & Fns entries, shuttles ok as they are
13824  {
13825  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13826  TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
13827  // use reference so can change internals
13828  if(AVEntry0.Command == "Sfs")
13829  {
13830  //new at v2.15.0. Only set location if have a forward and backward linkage at same time (these all set when SecondPassActions called). This isn't
13831  //rigorous as may have more than one, but if do then will be caught below in CheckCrossReferencesAndSetData
13832  //If fail to find location just ignore as will be caught later in CheckCrossReferencesAndSetData
13833  //note that at this stage the OtherHeadCode values are service refs, as haven't yet been changed back to headcodes until StripExcessFromHeadCode called
13834  //at end of this function
13835  //need to be the same: forward & reverse service refs, event times, commands correspond
13836 
13837  //successor checks first: /no starts, /finishes except Frh & Fjo (as of v2.15.0), /repeats, /pas, /TimeTimeLoc or /TimeLoc arr; any other cmd or TimeLoc (dep) OK
13838  if(TDEntry.ActionVector.size() < 2)
13839  {
13840  SecondPassMessage(GiveMessages, "Error in timetable - insufficient actions follwing an 'Sfs' event for: " + TDEntry.HeadCode);
13841  TrainDataVector.clear();
13842  Utilities->CallLogPop(2587);
13843  return(false);
13844  }
13845  TActionVectorEntry AVEntry1 = TDEntry.ActionVector.at(1);
13846  if(!AtLocSuccessor(AVEntry1))
13847  {
13848  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode +
13849  ". The event isn't valid for a stationary train.");
13850  TrainDataVector.clear();
13851  Utilities->CallLogPop(2588);
13852  return(false);
13853  }
13854  if((AVEntry1.SequenceType == StartSequence) || ((AVEntry1.SequenceType == FinishSequence) && (AVEntry1.Command != "Frh") && (AVEntry1.Command != "Fjo")) ||
13855  (AVEntry1.FormatType == Repeat))
13856  {
13857  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' event is followed by an illegal event for: " + TDEntry.HeadCode);
13858  TrainDataVector.clear();
13859  Utilities->CallLogPop(2589);
13860  return(false);
13861  }
13862 
13863  //now set the location and location type
13864  TDateTime SfsEventTime = AVEntry0.EventTime;
13865  AnsiString SfsServiceRef = TDEntry.ServiceReference;
13866  bool BreakFlag = false;
13867  for(unsigned int y = 0; y < TrainDataVector.size(); y++)
13868  {
13869  for(unsigned int z = 0; z < TrainDataVector.at(y).ActionVector.size(); z++)
13870  {
13871  if(((TrainDataVector.at(y).ActionVector.at(z).Command == "fsp") || (TrainDataVector.at(y).ActionVector.at(z).Command == "rsp")) &&
13872  (SfsEventTime == TrainDataVector.at(y).ActionVector.at(z).EventTime) && (TrainDataVector.at(y).HeadCode == AVEntry0.OtherHeadCode))
13873  { //forward linkage found
13874  if(TrainDataVector.at(y).ActionVector.at(z).OtherHeadCode == SfsServiceRef) //OtherHeadCode values are service refs, see above
13875  { //reverse linkage found
13876  AVEntry0.LocationName = TrainDataVector.at(y).ActionVector.at(z).LocationName;
13877  AVEntry0.LocationType = AtLocation;
13878  BreakFlag = true;
13879  break;
13880  }
13881  }
13882  }
13883  if(BreakFlag)
13884  {
13885  break;
13886  }
13887  } //test for any unnamed AtLoc entries at end of name setting
13888  }
13889  }
13890 
13891  // set all cmd locations based on earlier location name in TimeLoc arrival or Sfs/Sns/Sns-sh/Sns-fsh/located Snt/Snt-sh locations
13892  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13893  {
13894  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13895  {
13896  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13897  if(AVEntry.LocationName != "")
13898  {
13899  for(unsigned int z = y + 1; z < TrainDataVector.at(x).ActionVector.size(); z++)
13900  {
13901  TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(z);
13902  // use reference so can change internals where necessary
13903  if((AVEntry2.Command != "") && (AVEntry2.LocationType == AtLocation))
13904  {
13905  AVEntry2.LocationName = AVEntry.LocationName;
13906  } //test for any unnamed AtLoc entries at end of name setting
13907  else
13908  {
13909  break;
13910  }
13911  }
13912  }
13913  }
13914  }
13915  // all location names should now be set
13916 
13917 //now test for any unnamed AtLoc entries where Command != "" and give message if find any - shouldn't find any if above checks comprehensive
13918  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13919  {
13920  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13921  {
13922  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13923  if((AVEntry.LocationType == AtLocation) && (AVEntry.LocationName == "") && (AVEntry.Command != ""))
13924  {
13925  if((AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc"))
13926  {
13927  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "' must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
13928  TrainDataVector.clear();
13929  Utilities->CallLogPop(2619);
13930  return(false);
13931  }
13932  else if((AVEntry.Command == "Sns") || (AVEntry.Command == "Sfs"))
13933  {
13934  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
13935  "Please make sure that the finish event of the service that links to this event is preceded by an "
13936  "event at the same location that has an identified location name, normally an arrival.");
13937  TrainDataVector.clear();
13938  Utilities->CallLogPop(2620);
13939  return(false);
13940  }
13941  else if(AVEntry.Command == "Snt-sh")
13942  {
13943  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
13944  "Please make sure that the service starts with zero speed and is at a named location.");
13945  TrainDataVector.clear();
13946  Utilities->CallLogPop(2623);
13947  return(false);
13948  }
13949  else if((AVEntry.Command == "Sns-fsh") || (AVEntry.Command == "Sns-sh"))
13950  {
13951  SecondPassMessage(GiveMessages, "Error in timetable - the location of the '" + AVEntry.Command + "' event in service " + TrainDataVector.at(x).ServiceReference + " can't be identified. " +
13952  "Please make sure that the event is followed (not necessarily immediately) by a departure.");
13953  TrainDataVector.clear();
13954  Utilities->CallLogPop(2622);
13955  return(false);
13956  }
13957  else if((AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"))
13958  {
13959  SecondPassMessage(GiveMessages, "Error in timetable - '" + AVEntry.Command + "must be preceded by an event at the same location that has an identified location name, normally an arrival, see " + TrainDataVector.at(x).ServiceReference);
13960  TrainDataVector.clear();
13961  Utilities->CallLogPop(2621);
13962  return(false);
13963  }
13964  }
13965  }
13966  }
13967 
13968  // check remaining successor validity except for TimeLoc arr & dep since those times not set yet
13969  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
13970  {
13971  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
13972  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
13973  {
13974  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
13975  if((AVEntry.SequenceType == FinishSequence) && (AVEntry.Command != "F-nshs"))
13976  {
13977  if(y < (TrainDataVector.at(x).ActionVector.size() - 1))
13978  // i.e at least one more, must be a repeat
13979  {
13980  if(TrainDataVector.at(x).ActionVector.at(y + 1).FormatType != Repeat)
13981  {
13982  SecondPassMessage(GiveMessages, "Error in timetable - only a repeat can follow a finish event for: " + TDEntry.HeadCode);
13983  TrainDataVector.clear();
13984  Utilities->CallLogPop(798);
13985  return(false);
13986  }
13987  }
13988  }
13989  if(AVEntry.Command == "F-nshs")
13990  {
13991  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
13992  // i.e has to be the last
13993  {
13994  SecondPassMessage(GiveMessages, "Error in timetable - F-nshs (shuttle link) must be the last event for: " + TDEntry.HeadCode);
13995  TrainDataVector.clear();
13996  Utilities->CallLogPop(1049);
13997  return(false);
13998  }
13999  }
14000  if(AVEntry.Command == "pas")
14001  {
14002  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14003  {
14004  SecondPassMessage(GiveMessages, "Error in timetable - a 'pas' can't be the last event for: " + TDEntry.HeadCode);
14005  TrainDataVector.clear();
14006  Utilities->CallLogPop(1518);
14007  return(false);
14008  }
14009  }
14010  if(AVEntry.Command == "jbo")
14011  {
14012  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14013  {
14014  SecondPassMessage(GiveMessages, "Error in timetable - a 'jbo' can't be the last event for: " + TDEntry.HeadCode);
14015  TrainDataVector.clear();
14016  Utilities->CallLogPop(800);
14017  return(false);
14018  }
14019  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14020  if(!AtLocSuccessor(AVEntry2))
14021  {
14022  SecondPassMessage(GiveMessages, "Error in timetable - a jbo event is followed by an illegal event for: " + TDEntry.HeadCode +
14023  ". The event isn't valid for a stationary train.");
14024  TrainDataVector.clear();
14025  Utilities->CallLogPop(801);
14026  return(false);
14027  }
14028  }
14029  if((AVEntry.Command == "fsp") || (AVEntry.Command == "rsp"))
14030  {
14031  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14032  {
14033  SecondPassMessage(GiveMessages, "Error in timetable - a train split can't be the last event for: " + TDEntry.HeadCode);
14034  TrainDataVector.clear();
14035  Utilities->CallLogPop(802);
14036  return(false);
14037  }
14038  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14039  if(!AtLocSuccessor(AVEntry2))
14040  {
14041  SecondPassMessage(GiveMessages, "Error in timetable - a train split is followed by an illegal event for: " + TDEntry.HeadCode +
14042  ". The event isn't valid for a stationary train.");
14043  TrainDataVector.clear();
14044  Utilities->CallLogPop(803);
14045  return(false);
14046  }
14047  }
14048  if(AVEntry.Command == "cdt")
14049  {
14050  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14051  {
14052  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' can't be the last event for: " + TDEntry.HeadCode);
14053  TrainDataVector.clear();
14054  Utilities->CallLogPop(804);
14055  return(false);
14056  }
14057  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14058  if(!AtLocSuccessor(AVEntry2))
14059  {
14060  SecondPassMessage(GiveMessages, "Error in timetable - a 'cdt' is followed by an illegal event for: " + TDEntry.HeadCode +
14061  ". The event isn't valid for a stationary train.");
14062  TrainDataVector.clear();
14063  Utilities->CallLogPop(805);
14064  return(false);
14065  }
14066  }
14067  if(AVEntry.Command == "dsc")
14068  {
14069  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14070  {
14071  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' can't be the last event for: " + TDEntry.HeadCode);
14072  TrainDataVector.clear();
14073  Utilities->CallLogPop(2602);
14074  return(false);
14075  }
14076  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14077  if(!AtLocSuccessor(AVEntry2))
14078  {
14079  SecondPassMessage(GiveMessages, "Error in timetable - a 'dsc' is followed by an illegal event for: " + TDEntry.HeadCode +
14080  ". The event isn't valid for a stationary train.");
14081  TrainDataVector.clear();
14082  Utilities->CallLogPop(2603);
14083  return(false);
14084  }
14085  }
14086  if(AVEntry.FormatType == TimeTimeLoc)
14087  {
14088  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14089  {
14090  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure can't be the last event for: " + TDEntry.HeadCode);
14091  TrainDataVector.clear();
14092  Utilities->CallLogPop(806);
14093  return(false);
14094  }
14095  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14096  if(!MovingSuccessor(AVEntry2))
14097  {
14098  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure is followed by an illegal event for: " +
14099  TDEntry.HeadCode + ". The event isn't valid for a moving train.");
14100  TrainDataVector.clear();
14101  Utilities->CallLogPop(807);
14102  return(false);
14103  }
14104  }
14105  if(AVEntry.FormatType == PassTime)
14106  {
14107  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14108  {
14109  SecondPassMessage(GiveMessages, "Error in timetable - a pass time can't be the last event for: " + TDEntry.HeadCode);
14110  TrainDataVector.clear();
14111  Utilities->CallLogPop(1530);
14112  return(false);
14113  }
14114  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14115  if(!MovingSuccessor(AVEntry2))
14116  {
14117  SecondPassMessage(GiveMessages, "Error in timetable - a pass time is followed by an illegal event for: " + TDEntry.HeadCode +
14118  ". The event isn't valid for a moving train.");
14119  TrainDataVector.clear();
14120  Utilities->CallLogPop(1531);
14121  return(false);
14122  }
14123  }
14124  if(AVEntry.FormatType == Repeat)
14125  {
14126  if(y != (TrainDataVector.at(x).ActionVector.size() - 1))
14127  {
14128  SecondPassMessage(GiveMessages, "Error in timetable - a repeat is not the last item for: " + TDEntry.HeadCode);
14129  TrainDataVector.clear();
14130  Utilities->CallLogPop(808);
14131  return(false);
14132  }
14133  }
14134  }
14135  }
14136 
14137  // set arrival & departure times for TimeLocs & set their EventTimes to -1
14138  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14139  {
14140  bool LastEntryIsAnArrival = false;
14141  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14142  // first deal with unlocated Snt entries - so next entry (TimeLoc or TimeTimeLoc) is an arrival, all else stopped so the next TimeLoc is a departure
14143  const TActionVectorEntry &AVEntry0 = TrainDataVector.at(x).ActionVector.at(0);
14144  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14145  // StartSpeed may or may not be 0, but train will move forwards (if capable of doing so), & next TimeLoc will be an arrival, whether or not after one or more TimeTimeLocs
14146  {
14147  LastEntryIsAnArrival = false;
14148  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14149  {
14150  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14151  if(AVEntry.FormatType == TimeLoc)
14152  {
14153  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14154  {
14155  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14156  }
14157  if(LastEntryIsAnArrival)
14158  {
14159  AVEntry.DepartureTime = AVEntry.EventTime;
14160  AVEntry.EventTime = TDateTime(-1);
14161  LastEntryIsAnArrival = false;
14162  }
14163  else // last entry a departure
14164  {
14165  AVEntry.ArrivalTime = AVEntry.EventTime;
14166  AVEntry.EventTime = TDateTime(-1);
14167  LastEntryIsAnArrival = true;
14168  }
14169  }
14170  }
14171  }
14172  else // all others stopped at beginning
14173  {
14174  LastEntryIsAnArrival = true;
14175  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14176  {
14177  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14178  if(AVEntry.FormatType == TimeLoc)
14179  {
14180  if((AVEntry.ArrivalTime > TDateTime(-1)) || (AVEntry.DepartureTime > TDateTime(-1)) || (AVEntry.EventTime == TDateTime(-1)))
14181  {
14182  throw Exception("Timetable error, TimeLoc times not as initially set for " + TDEntry.HeadCode);
14183  }
14184  if(LastEntryIsAnArrival)
14185  {
14186  AVEntry.DepartureTime = AVEntry.EventTime;
14187  AVEntry.EventTime = TDateTime(-1);
14188  LastEntryIsAnArrival = false;
14189  }
14190  else // last entry a departure
14191  {
14192  AVEntry.ArrivalTime = AVEntry.EventTime;
14193  AVEntry.EventTime = TDateTime(-1);
14194  LastEntryIsAnArrival = true;
14195  }
14196  }
14197  }
14198  }
14199  }
14200  // perform remaining successor checks for TimeLocs
14201  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14202  {
14203  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14204  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14205  {
14206  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14207  if((AVEntry.FormatType == TimeLoc) && (AVEntry.ArrivalTime >= TDateTime(0))) // arrival
14208  // TimeLoc (arr) -> No starts, repeats, Fer or TimeTimeLoc; TimeLoc (dep) or any other OK
14209  {
14210  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14211  {
14212  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival can't be the last event for: " + TDEntry.HeadCode);
14213  TrainDataVector.clear();
14214  Utilities->CallLogPop(809);
14215  return(false);
14216  }
14217  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14218  if(!AtLocSuccessor(AVEntry2))
14219  {
14220  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival is followed by an illegal event for: " + TDEntry.HeadCode +
14221  ". The event isn't valid for a stationary train.");
14222  TrainDataVector.clear();
14223  Utilities->CallLogPop(810);
14224  return(false);
14225  }
14226  }
14227  if((AVEntry.FormatType == TimeLoc) && (AVEntry.DepartureTime >= TDateTime(0))) // departure
14228  // TimeLoc (dep) -> Fer, TimeLoc (arr), TimeTimeLoc, (new) pas OK, no others
14229  {
14230  if(y >= (TrainDataVector.at(x).ActionVector.size() - 1))
14231  {
14232  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure can't be the last event for: " + TDEntry.HeadCode);
14233  TrainDataVector.clear();
14234  Utilities->CallLogPop(811);
14235  return(false);
14236  }
14237  const TActionVectorEntry &AVEntry2 = TrainDataVector.at(x).ActionVector.at(y + 1);
14238  if(!MovingSuccessor(AVEntry2))
14239  {
14240  SecondPassMessage(GiveMessages, "Error in timetable - a timed departure is followed by an illegal event for: " + TDEntry.HeadCode +
14241  ". The event isn't valid for a moving train.");
14242  TrainDataVector.clear();
14243  Utilities->CallLogPop(812);
14244  return(false);
14245  }
14246  }
14247  }
14248  }
14249 
14250  // check all TimeLocs have either Arr or Dep time set and EventTime == -1, all Cmds have EventTime set & Arr & Dep times == -1,
14251  // & repeats have no times set
14252  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14253  {
14254  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14255  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14256  {
14257  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14258  if(AVEntry.FormatType == TimeLoc)
14259  {
14260  if(AVEntry.EventTime != TDateTime(-1))
14261  {
14262  throw Exception("Timetable error, TimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
14263  }
14264  if((AVEntry.ArrivalTime == TDateTime(-1)) && (AVEntry.DepartureTime == TDateTime(-1)))
14265  {
14266  throw Exception("Timetable error, TimeLoc event has neither arrival nor departure time set for " + TDEntry.HeadCode);
14267  }
14268  }
14269  if(AVEntry.FormatType == TimeTimeLoc)
14270  {
14271  if(AVEntry.EventTime != TDateTime(-1))
14272  {
14273  throw Exception("Timetable error, TimeTimeLoc event has EventTime not -1 for " + TDEntry.HeadCode);
14274  }
14275  if((AVEntry.ArrivalTime == TDateTime(-1)) || (AVEntry.DepartureTime == TDateTime(-1)))
14276  {
14277  throw Exception("Timetable error, TimeTimeLoc event has either arrival or departure time not set for " + TDEntry.HeadCode);
14278  }
14279  }
14280  if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == StartNew) ||
14281  (AVEntry.FormatType == SNTShuttle) || (AVEntry.FormatType == SNSShuttle) || (AVEntry.FormatType == FNSNonRepeatToShuttle) ||
14282  (AVEntry.FormatType == FSHNewService) || (AVEntry.FormatType == PassTime))
14283  {
14284  if(AVEntry.EventTime == TDateTime(-1))
14285  {
14286  throw Exception("Timetable error, Cmd or PassTime event has EventTime not set for " + TDEntry.HeadCode);
14287  }
14288  if((AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
14289  {
14290  throw Exception("Timetable error, Cmd or PassTime event has either arrival or departure time set for " + TDEntry.HeadCode);
14291  }
14292  }
14293  if(AVEntry.FormatType == Repeat)
14294  {
14295  if((AVEntry.EventTime != TDateTime(-1)) || (AVEntry.ArrivalTime != TDateTime(-1)) || (AVEntry.DepartureTime != TDateTime(-1)))
14296  {
14297  throw Exception("Timetable error, Repeat event has a time set for " + TDEntry.HeadCode);
14298  }
14299  }
14300  }
14301  }
14302 
14303  // check times stay same or increase, note that can have time of 0 if include midnight
14304  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14305  {
14306  TDateTime CurrentTime = TTClockTime; // the timetable start time
14307  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14308  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14309  {
14310  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14311  if(AVEntry.FormatType == Repeat)
14312  {
14313  break;
14314  }
14315  if(AVEntry.FormatType == FinRemHere)
14316  {
14317  break;
14318  }
14319  if(AVEntry.FormatType == TimeTimeLoc)
14320  {
14321  if(AVEntry.DepartureTime < AVEntry.ArrivalTime)
14322  {
14323  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has a later arrival than departure time for: " +
14324  TDEntry.HeadCode);
14325  TrainDataVector.clear();
14326  Utilities->CallLogPop(813);
14327  return(false);
14328  }
14329  if(AVEntry.ArrivalTime < CurrentTime)
14330  {
14331  SecondPassMessage(GiveMessages, "Error in timetable - a timed arrival and departure has too early an arrival time for: " +
14332  TDEntry.HeadCode);
14333  TrainDataVector.clear();
14334  Utilities->CallLogPop(814);
14335  return(false);
14336  }
14337  CurrentTime = AVEntry.DepartureTime;
14338  continue;
14339  }
14340  if(AVEntry.FormatType == TimeLoc)
14341  {
14342  if(AVEntry.ArrivalTime >= TDateTime(0))
14343  {
14344  if(AVEntry.ArrivalTime < CurrentTime)
14345  {
14346  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
14347  TrainDataVector.clear();
14348  Utilities->CallLogPop(815);
14349  return(false);
14350  }
14351  CurrentTime = AVEntry.ArrivalTime;
14352  }
14353  else
14354  {
14355  if(AVEntry.DepartureTime < CurrentTime)
14356  // both may be 0 legitimately so must allow for this
14357  {
14358  SecondPassMessage(GiveMessages, "Error in timetable - a timed location event has a time that is too early for: " + TDEntry.HeadCode);
14359  TrainDataVector.clear();
14360  Utilities->CallLogPop(816);
14361  return(false);
14362  }
14363  CurrentTime = AVEntry.DepartureTime;
14364  }
14365  continue;
14366  }
14367  if(AVEntry.EventTime < CurrentTime)
14368  // all others have EventTime set
14369  {
14370  SecondPassMessage(GiveMessages, "Error in timetable - a train event has a time that is set too early for: " + TDEntry.HeadCode +
14371  ", may be before timetable start time");
14372  TrainDataVector.clear();
14373  Utilities->CallLogPop(835);
14374  return(false);
14375  }
14376  CurrentTime = AVEntry.EventTime;
14377  continue;
14378  }
14379  }
14380 
14381  // check locations consistent
14382  AnsiString LastLocationName = "";
14383 
14384  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14385  {
14386  bool LastEntryIsAnArrival = false;
14387  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14388  // first deal with moving Snt entries (all else stopped)
14389  if((TrainDataVector.at(x).ActionVector.at(0).Command == "Snt") && (TrainDataVector.at(x).ActionVector.at(0).LocationType == EnRoute))
14390  {
14391  LastEntryIsAnArrival = false;
14392  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName; // should be ""
14393  if(LastLocationName != "")
14394  {
14395  throw Exception("Timetable error, moving Snt event has LocationName set for " + TDEntry.HeadCode);
14396  }
14397  for(unsigned int y = 1; y < TrainDataVector.at(x).ActionVector.size();
14398  y++) // note that immediate successor to a moving Snt can only be a Moving type
14399  {
14400  // if it's a SignallerControl entry then the condition isn't met
14401  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14402  if(AVEntry.FormatType == Repeat)
14403  {
14404  break; // repeat = reached end (+allows repeat after signaller controlled entry)
14405  }
14406  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
14407  {
14408  if(AVEntry.LocationName != LastLocationName)
14409  {
14410  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14411  AVEntry.Command);
14412  TrainDataVector.clear();
14413  Utilities->CallLogPop(823);
14414  return(false);
14415  }
14416  }
14417  else if((AVEntry.FormatType == TimeCmd) || (AVEntry.FormatType == TimeCmdDescription))
14418  // cdt is the only TimeCmd & dsc is the only TimeCmdDescription
14419  {
14420  if(AVEntry.LocationName != LastLocationName)
14421  {
14422  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14423  AVEntry.Command);
14424  TrainDataVector.clear();
14425  Utilities->CallLogPop(824);
14426  return(false);
14427  }
14428  }
14429  else if(AVEntry.FormatType == TimeTimeLoc)
14430  {
14431  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14432  // last entry must be a departure or would have failed earlier
14433  {
14434  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14435  TwoLocationFlag = true;
14436 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14437 // TwoOrMoreLocationsWarningGiven = true;
14438  }
14439  LastLocationName = AVEntry.LocationName;
14440  LastEntryIsAnArrival = false;
14441  }
14442  else if(AVEntry.FormatType == TimeLoc)
14443  {
14444  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
14445  {
14446  SecondPassMessage(GiveMessages,
14447  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
14448  TrainDataVector.clear();
14449  Utilities->CallLogPop(826);
14450  return(false);
14451  }
14452  else if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName))
14453  {
14454  SecondPassMessage(GiveMessages,
14455  "Error in timetable - a location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode);
14456  TrainDataVector.clear();
14457  Utilities->CallLogPop(827);
14458  return(false);
14459  }
14460  LastLocationName = AVEntry.LocationName;
14461  LastEntryIsAnArrival = !LastEntryIsAnArrival;
14462  }
14463  }
14464  }
14465  else // all stationary starting entries
14466  {
14467  LastEntryIsAnArrival = true;
14468  LastLocationName = TrainDataVector.at(x).ActionVector.at(0).LocationName;
14469  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14470  {
14471  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14472  if(AVEntry.FormatType == Repeat)
14473  {
14474  break;
14475  }
14476  else if((AVEntry.FormatType == TimeCmdHeadCode) || (AVEntry.FormatType == FNSNonRepeatToShuttle))
14477  // no need to add anything for shuttle starts since they are at loc (0) anyway
14478  {
14479  if(AVEntry.LocationName != LastLocationName)
14480  {
14481  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14482  AVEntry.Command);
14483  TrainDataVector.clear();
14484  Utilities->CallLogPop(828);
14485  return(false);
14486  }
14487  }
14488  else if(AVEntry.FormatType == TimeCmd)
14489  // cdt is the only TimeCmd
14490  {
14491  if(AVEntry.LocationName != LastLocationName)
14492  {
14493  SecondPassMessage(GiveMessages, "Error in timetable - a location event is inconsistent for: " + TDEntry.HeadCode + " && " +
14494  AVEntry.Command);
14495  TrainDataVector.clear();
14496  Utilities->CallLogPop(829);
14497  return(false);
14498  }
14499  }
14500  else if(AVEntry.FormatType == TimeTimeLoc)
14501  {
14502  if((AVEntry.LocationName == LastLocationName) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14503  // last entry must be a departure or would have failed earlier
14504  {
14505  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14506  TwoLocationFlag = true;
14507 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14508 // TwoOrMoreLocationsWarningGiven = true;
14509  }
14510  LastLocationName = AVEntry.LocationName;
14511  LastEntryIsAnArrival = false;
14512  }
14513  else if(AVEntry.FormatType == TimeLoc)
14514  {
14515  if(LastEntryIsAnArrival && (AVEntry.LocationName != LastLocationName))
14516  {
14517  SecondPassMessage(GiveMessages,
14518  "Error in timetable - a location event for a timed departure is different from the arrival location for: " + TDEntry.HeadCode);
14519  TrainDataVector.clear();
14520  Utilities->CallLogPop(831);
14521  return(false);
14522  }
14523  if(!LastEntryIsAnArrival && (AVEntry.LocationName == LastLocationName) && !TwoOrMoreLocationsWarningGiven)
14524  {
14525  SecondPassMessage(GiveMessages,
14526  "A location event for a timed arrival is the same as the earlier departure location for: " + TDEntry.HeadCode + ". Please correct if this is an error.\n\nThis warning will not be shown again.");
14528 // TrainDataVector.clear();
14529 // Utilities->CallLogPop(832);
14530 // return false;
14531  }
14532  LastLocationName = AVEntry.LocationName;
14533  LastEntryIsAnArrival = !LastEntryIsAnArrival;
14534  }
14535  }
14536  }
14537  }
14538 
14539  // Check same location doesn't appear twice before a cdt except for separate arr & dep TimeLocs (just a potential error warning given in v2.6.0)
14540  // i.e. same location can appear in any number of consecutive entries but once changed couldn't repeat before a direction change prior to v2.6.0
14541  AnsiString LocationNameToBeChecked = "";
14542 
14543  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14544  {
14545  const TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14546  unsigned int y = 0;
14547  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
14548  // first discard unlocated Snt entries as they don't have location name set
14549  if((AVEntry0.Command == "Snt") && (AVEntry0.LocationType == EnRoute))
14550  {
14551  y = 1;
14552  }
14553  while(y < TDEntry.ActionVector.size())
14554  // need to check each location name separately in turn, skipped for SignallerControl entries
14555  {
14556  if((TDEntry.ActionVector.at(y).Command == "Fer") || (TDEntry.ActionVector.at(y).FormatType == Repeat))
14557  {
14558  break; // out of the 'while' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
14559  }
14560  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName;
14561  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
14562  {
14563  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
14564  if((AVEntry.Command == "Fer") || (AVEntry.FormatType == Repeat))
14565  {
14566  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
14567  }
14568  if(AVEntry.Command == "cdt")
14569  {
14570  break; // out of the 'z' loop since the check is only valid up to a change of direction
14571  }
14572  if(AVEntry.LocationName == LocationNameToBeChecked)
14573  {
14574  continue; // keep going while name same
14575  }
14576  if(AVEntry.LocationName != LocationNameToBeChecked)
14577  // if name different check forwards to see if repeats
14578  {
14579  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
14580  {
14581  if(TDEntry.ActionVector.at(a).Command == "cdt")
14582  {
14583  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
14584  }
14585  if((TDEntry.ActionVector.at(a).LocationName == LocationNameToBeChecked) && TTEditPanelVisible) //changed at v2.6.0 to allow loops & consecutive same locs
14586  {
14587  TwoLocationList.insert(TwoLocationList.end(), TDEntry.ServiceReference); //added at v2.9.1
14588  TwoLocationFlag = true;
14589 // ShowMessage("Two or more locations are the same without a change of direction between them. Please correct if this is an error.\n\nThis warning will not be shown again.");
14590 // TwoOrMoreLocationsWarningGiven = true;
14591  }
14592  }
14593  break; // out of the 'z' loop since have checked 'a' as far as need to
14594  }
14595  }
14596  y++;
14597  }
14598  }
14599  if(TwoLocationFlag) //messages for this moved to InterfaceUnit - separate box listing potential errors and asking user to check
14600  {
14601  TwoLocationList.sort(); //need to sort first in alphabetical order to ensure all duplictes removed
14602  TwoLocationList.unique(); //remove duplicates
14603  }
14604 
14605  // check all locations except unlocated 'Snt' & 'Fer' have LocationName set
14606  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14607  {
14608  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14609  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14610  {
14611  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14612  if((AVEntry.LocationName == "") && (AVEntry.Command != "Snt") && (AVEntry.Command != "Fer") && (AVEntry.FormatType != Repeat))
14613  {
14614  throw Exception("Error, non- 'Snt', 'Fer' or Repeat event doesn't have a location name set for " + TDEntry.HeadCode);
14615  }
14616  AnsiString LocName = "";
14617  // dummy, only used so can call IsSNTEntryLocated
14618  if((AVEntry.Command == "Snt") && (IsSNTEntryLocated(1, TrainDataVector.at(x), LocName)))
14619  {
14620  if(AVEntry.LocationName == "")
14621  {
14622  throw Exception("Error, 'Snt' event at a stop location doesn't have a location name set for " + TDEntry.HeadCode);
14623  }
14624  }
14625  if((AVEntry.Command == "Snt") && !(IsSNTEntryLocated(2, TrainDataVector.at(x), LocName)))
14626  {
14627  if(AVEntry.LocationName != "")
14628  {
14629  throw Exception("Error, 'Snt' unlocated event has a location name set for " + TDEntry.HeadCode);
14630  }
14631  }
14632  }
14633  }
14634 
14635 /* Check a split to 'x' doesn't again split to 'x' (anywhere, not just for one train, since headcodes can be duplicated)
14636  Check each joined by train not joined by same train again (anywhere, not just for one train, since headcodes can be duplicated)
14637  Check each change of headcode not repeated anywhere else (anywhere, not just for one train, since headcodes can be duplicated)
14638 
14639  i.e. check everywhere where there is an 'OtherHeadCode' that it matches once only with its reference (both ways) + set
14640  the OtherHeadCodeStartingEntryPtr pointers where appropriate + train information for splits & new services
14641 
14642  BUT need to separate the shuttles from non-shuttles, because can have two trains reference each other in both forms,
14643  eg 2F44 Sns-sh ends in Fns to 2F45, & Sns 2F45 ends in Fns-sh to 2F44. Here 2F45 is the 'OtherHeadCode' for both
14644  Sns-sh & Fns in train 2F44, & 2F44 is the 'OtherHeadCode' for both Sns & Fns-sh in train 2F45.
14645 */
14646  for(unsigned int x = 0; x < TrainDataVector.size(); x++) // new test to ensure no duplicate links at all, other checks ensure none for shuttles,
14647  {
14648  // non-shuttles & non-repeating links separately, but don't check that there isn't a
14649  // duplicate between a non-repeating shuttle and another - leave original tests in as
14650  // these also set the pointers
14651  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14652  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14653  {
14654  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14655  if(AVEntry.OtherHeadCode != "")
14656  {
14657  if(!CheckForDuplicateCrossReferences(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, GiveMessages))
14658  {
14659  Utilities->CallLogPop(1584);
14660  return(false); // error message given in called function
14661  }
14662  }
14663  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14664  {
14665  if(!CheckForDuplicateCrossReferences(1, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, GiveMessages))
14666  {
14667  Utilities->CallLogPop(1585);
14668  return(false); // error message given in called function
14669  }
14670  }
14671  }
14672  }
14673 
14674  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14675  {
14676  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14677  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14678  {
14679  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14680  if((AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
14681  {
14682  if(AVEntry.OtherHeadCode != "")
14683  {
14684  if(!CheckCrossReferencesAndSetData(0, TDEntry.HeadCode, AVEntry.OtherHeadCode, false, true, GiveMessages))
14685  // false = non-shuttle
14686  {
14687  Utilities->CallLogPop(864);
14688  return(false); // error message given in called function
14689  }
14690  }
14691  }
14692  }
14693  }
14694 
14695  // now repeat the check just for the shuttles
14696  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14697  {
14698  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14699  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14700  {
14701  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14702  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh"))
14703  {
14704  if(AVEntry.OtherHeadCode != "")
14705  {
14706  if(!CheckCrossReferencesAndSetData(1, TDEntry.HeadCode, AVEntry.OtherHeadCode, true, true, GiveMessages))
14707  // true = shuttle
14708  {
14709  Utilities->CallLogPop(1100);
14710  return(false); // error message given in called function
14711  }
14712  }
14713  }
14714  }
14715  }
14716 
14717  // check for proper non-repeating link cross references and that they have no repeats & that times are consistent
14718  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14719  {
14720  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14721  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14722  {
14723  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14724  if(AVEntry.NonRepeatingShuttleLinkHeadCode != "")
14725  {
14726  if(!CheckNonRepeatingShuttleLinksAndSetData(0, TDEntry.HeadCode, AVEntry.NonRepeatingShuttleLinkHeadCode, true, GiveMessages))
14727  {
14728  Utilities->CallLogPop(1060);
14729  return(false); // error message given in called function
14730  }
14731  }
14732  }
14733  }
14734 
14735  // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
14736  // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
14737  // don't ever need to and as designed would skip repeats
14738  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14739  {
14740  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14741  {
14742  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14743  if((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh"))
14744  {
14745  if(!CheckShuttleServiceIntegrity(0, &(TrainDataVector.at(x)), GiveMessages))
14746  {
14747  Utilities->CallLogPop(1090);
14748  return(false); // error message given in called function
14749  }
14750  }
14751  }
14752  }
14753 
14754  // check all entries have all types set to something
14755  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14756  {
14757  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14758  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14759  {
14760  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14761  if(AVEntry.FormatType == NoFormat)
14762  {
14763  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has FormatType unset for: " + TDEntry.HeadCode);
14764  }
14765  else if(AVEntry.SequenceType == NoSequence)
14766  {
14767  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has SequenceType unset for: " + TDEntry.HeadCode);
14768  }
14769  else if(AVEntry.LocationType == NoLocation)
14770  {
14771  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has LocationType unset for: " + TDEntry.HeadCode);
14772  }
14773  else if(AVEntry.ShuttleLinkType == NoShuttleLink)
14774  {
14775  throw Exception("Error - timetable ActionVector event no. " + AnsiString(y) + " has ShuttleLinkType unset for: " + TDEntry.HeadCode);
14776  }
14777  }
14778  }
14779 
14780  // all OK if reach here, so set up the TrainOperatingDataVector (already has one entry) & NumberOfTrains
14781  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14782  {
14783  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
14784  // non-const reference so can alter content
14785  TTrainOperatingData TData;
14786  const TActionVectorEntry &LastAVEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
14787  if(LastAVEntry.FormatType == Repeat) // check if a repeat
14788  {
14789 /*
14790  class TTrainOperatingData
14791  {
14792  public:
14793  int TrainID; - default, set at construction
14794  TActionEventType EventReported; used during operation
14795  TRunningEntry RunningEntry; - default, set at construction
14796  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;} //constructor, values set to defaults
14797  };
14798 */
14799  TDEntry.NumberOfTrains = LastAVEntry.NumberOfRepeats + 1;
14800  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
14801  {
14802  TDEntry.TrainOperatingDataVector.push_back(TData);
14803  }
14804  }
14805  else
14806  {
14807  TDEntry.NumberOfTrains = 1;
14808  }
14809  }
14810 
14811  // check that don't include any Continuation names
14812  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14813  {
14814  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14815  {
14816  AnsiString LocName = TrainDataVector.at(x).ActionVector.at(y).LocationName;
14817  AnsiString HC = TrainDataVector.at(x).HeadCode;
14818  if(LocName != "")
14819  {
14820  if(Track->ContinuationNameMap.find(LocName) != Track->ContinuationNameMap.end())
14821  {
14822  SecondPassMessage(GiveMessages, "Error in timetable - continuation names (" + LocName + ") must not be included, see service " + HC);
14823  TrainDataVector.clear();
14824  Utilities->CallLogPop(1578);
14825  return(false);
14826  }
14827  }
14828  }
14829  }
14830 
14831  // check that all repeat times below 96h
14832  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14833  {
14834  int NumRepeats = (TrainDataVector.at(x).NumberOfTrains) - 1;
14835  int IncMinutes = 0;
14836  if(TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).FormatType == Repeat)
14837  {
14838  IncMinutes = TrainDataVector.at(x).ActionVector.at(TrainDataVector.at(x).ActionVector.size() - 1).RearStartOrRepeatMins;
14839  }
14840  else
14841  {
14842  continue; // basic times already checked in CheckTimeValidity
14843  }
14844  AnsiString HC = TrainDataVector.at(x).HeadCode;
14845  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14846  {
14847  if((double)TrainDataVector.at(x).ActionVector.at(y).EventTime > -1)
14848  {
14849  if(((double)GetRepeatTime(32, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
14850  {
14851  SecondPassMessage(GiveMessages, "Error in timetable - a repeat time exceeds 95h 59m, see service " + HC); // 3d 23h 59m = 3.9993055556
14852  TrainDataVector.clear();
14853  Utilities->CallLogPop(1818);
14854  return(false);
14855  }
14856  }
14857  if((double)TrainDataVector.at(x).ActionVector.at(y).ArrivalTime > -1)
14858  {
14859  if(((double)GetRepeatTime(33, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
14860  // 3d 23h 59m = 3.9993055556
14861  {
14862  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
14863  TrainDataVector.clear();
14864  Utilities->CallLogPop(1819);
14865  return(false);
14866  }
14867  }
14868  if((double)TrainDataVector.at(x).ActionVector.at(y).DepartureTime > -1)
14869  {
14870  if(((double)GetRepeatTime(34, TrainDataVector.at(x).ActionVector.at(y).EventTime, NumRepeats, IncMinutes) >= 3.9994))
14871  // 3d 23h 59m = 3.9993055556
14872  {
14873  SecondPassMessage(GiveMessages, "Error in timetable - a repeat event time exceeds 95h 59m, see service " + HC);
14874  TrainDataVector.clear();
14875  Utilities->CallLogPop(1820);
14876  return(false);
14877  }
14878  }
14879  }
14880  }
14881 
14882  // Now that all set up change any extended headcodes back to ordinary headcodes
14883  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14884  {
14885  StripExcessFromHeadCode(0, TrainDataVector.at(x).HeadCode);
14886  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14887  {
14888  StripExcessFromHeadCode(1, TrainDataVector.at(x).ActionVector.at(y).OtherHeadCode);
14889  StripExcessFromHeadCode(2, TrainDataVector.at(x).ActionVector.at(y).NonRepeatingShuttleLinkHeadCode);
14890  }
14891  }
14892 
14893  // SaveTrainDataVectorToFile(0);//for testing purposes
14895  Utilities->CallLogPop(782);
14896  return(true);
14897 }
14898 
14899 // ---------------------------------------------------------------------------
14900 // Moving successors: TimeLoc arr/TimeTimeLoc/pas/Fer;
14902 {
14903  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.FormatType == TimeTimeLoc) || (AVEntry.Command == "pas") || (AVEntry.Command == "Fer"));
14904 }
14905 
14906 // ---------------------------------------------------------------------------
14907 // AtLoc successors: TimeLoc dep/jbo/fsp/rsp/cdt/dsc/Frh/Fns/Fjo/Frh-sh/Fns-sh/F-nshs;
14909 {
14910  return ((AVEntry.FormatType == TimeLoc) || (AVEntry.Command == "jbo") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") ||
14911  (AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "Frh") || (AVEntry.Command == "Fns") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh-sh") ||
14912  (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "F-nshs"));
14913 }
14914 
14915 // ---------------------------------------------------------------------------
14916 
14917 void TTrainController::StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
14918 {
14919  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripExcessFromHeadCode," + HeadCode);
14920  if(HeadCode.Length() > 4) // ignore otherwise
14921  {
14922  HeadCode = HeadCode.SubString(HeadCode.Length() - 3, 4);
14923  }
14924  Utilities->CallLogPop(1593);
14925 }
14926 
14927 // ---------------------------------------------------------------------------
14928 
14929 bool TTrainController::CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
14930 {
14931  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckForDuplicateCrossReferences," + MainHeadCode + "," +
14932  SecondHeadCode);
14933  int ForwardCount = 0;
14934  int ReverseCount = 0;
14935 
14936  if(MainHeadCode == SecondHeadCode)
14937  {
14938  SecondPassMessage(GiveMessages, "Error in timetable - Service " + MainHeadCode + " has an event that references itself");
14939  TrainDataVector.clear();
14940  Utilities->CallLogPop(1594);
14941  return(false);
14942  }
14943  // forward check
14944  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14945  {
14946  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14947  if(TDEntry.HeadCode == MainHeadCode)
14948  {
14949  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14950  {
14951  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14952  if(AVEntry.OtherHeadCode == SecondHeadCode)
14953  {
14954  ForwardCount++;
14955  }
14956  if(AVEntry.NonRepeatingShuttleLinkHeadCode == SecondHeadCode)
14957  // need own check in case both 'Other' & 'NonRepeating' have same headcode
14958  {
14959  ForwardCount++;
14960  }
14961  }
14962  }
14963  }
14964  if(ForwardCount == 0)
14965  // this is an exception because the headcodes are selected in the same order as the forward check
14966  {
14967  throw Exception("Error, ForwardCount == 0 in CheckForDuplicateCrossReferences after called with found values");
14968  }
14969  if(ForwardCount > 2)
14970  // can have 2 if one is Sns-sh linking from another leg of the shuttle, and Fns links out to that same leg
14971  {
14972  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + SecondHeadCode + " from a train whose headcode is " +
14973  MainHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
14974  TrainDataVector.clear();
14975  Utilities->CallLogPop(1587);
14976  return(false);
14977  }
14978  // reverse check
14979  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
14980  {
14981  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
14982  if(TDEntry.HeadCode == SecondHeadCode)
14983  {
14984  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
14985  {
14986  const TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
14987  if(AVEntry.OtherHeadCode == MainHeadCode)
14988  {
14989  ReverseCount++;
14990  }
14991  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
14992  {
14993  ReverseCount++;
14994  }
14995  }
14996  }
14997  }
14998 
14999  if(ReverseCount == 0)
15000  {
15001  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + SecondHeadCode);
15002  TrainDataVector.clear();
15003  Utilities->CallLogPop(1588);
15004  return(false);
15005  }
15006  if(ReverseCount > 2)
15007  // can have 2 if one is a second shuttle leg with a link in from Fns, and it links out to the same service with Fxx-sh
15008  {
15009  SecondPassMessage(GiveMessages, "Error in timetable - found more than two references to " + MainHeadCode + " from a train whose headcode is " +
15010  SecondHeadCode + ". Check the service cross references from each service, and check whether one or other service is listed twice or more.");
15011  TrainDataVector.clear();
15012  Utilities->CallLogPop(1589);
15013  return(false);
15014  }
15015  if(ForwardCount != ReverseCount)
15016  {
15017  SecondPassMessage(GiveMessages, "Error in timetable - " + MainHeadCode + " has a different number of references to " + SecondHeadCode +
15018  " than the other way round");
15019  TrainDataVector.clear();
15020  Utilities->CallLogPop(1610);
15021  return(false);
15022  }
15023  Utilities->CallLogPop(1590);
15024  return(true);
15025 }
15026 
15027 // ---------------------------------------------------------------------------
15028 
15029 bool TTrainController::CheckCrossReferencesAndSetData(int Caller, AnsiString MainHeadCode, AnsiString OtherHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
15030 /* Return false for no find or more than one find, check correct types of link
15031  First run through all trains whose headcode is the MainHeadCode (may be > 1) & for each entry whose
15032  'other' is OtherHeadCode increment a forward counter. Keep a pointer to the 'OtherHeadCode' entry for use later
15033  Must be exactly 1 forward count. NB Forward relates to MainHeadCode
15034  Then do the same in reverse.
15035  Using the pointers check the event times, then check that the locations & commands match - if main is a split then other must be Sfs;
15036  if main is Fns other must be Sns; if main is jbo other must be Fjo.
15037  Also check platform lengths OK for a split location (call to Track function for this - at least one platform at location has to be long
15038  enough). If all succeeds so far set the relevant OtherHeadCodeStartingEntryPtr to the new service starting point + train information
15039  for Sfs & Sns services. Finally check the repeat entries if present are consistent
15040 
15041  Check all except the NonRepeatingShuttleLinkHeadCodes, which only occur from F-nshs to Sns-sh, and from Fns-sh to
15042  Sns-fsh. All others should check out OK, but check shuttles & non-shuttles separately.
15043 
15044  /NB prohibit main & other headcodes being same, causes probs in failing to recognise locations
15045 */
15046 
15047 {
15048  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckCrossReferencesAndSetData," + MainHeadCode + "," + OtherHeadCode);
15049  int ForwardCount = 0;
15050  int ReverseCount = 0;
15051  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
15052  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
15053  TTrainDataEntry *MainTrainDataPtr = 0;
15054  TTrainDataEntry *OtherTrainDataPtr = 0;
15055 
15056  // forward check
15057  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15058  {
15059  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15060  if(TDEntry.HeadCode == MainHeadCode)
15061  {
15062  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15063  {
15064  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15065  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15066  {
15067  if(AVEntry.OtherHeadCode == OtherHeadCode)
15068  {
15069  MainTrainDataPtr = &TrainDataVector.at(x);
15070  ForwardEntryPtr = &AVEntry;
15071  ForwardCount++;
15072  ForwardTDVectorNumber = x;
15073  }
15074  }
15075  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") ||
15076  (AVEntry.Command == "Frh-sh")))
15077  {
15078  if(AVEntry.OtherHeadCode == OtherHeadCode)
15079  {
15080  MainTrainDataPtr = &TrainDataVector.at(x);
15081  ForwardEntryPtr = &AVEntry;
15082  ForwardCount++;
15083  ForwardTDVectorNumber = x;
15084  }
15085  }
15086  }
15087  }
15088  }
15089  if(ForwardCount == 0)
15090  // this is an exception because the headcodes are selected in the same order as the forward check
15091  {
15092  throw Exception("Error, ForwardCount == 0 in CheckCrossReferencesAndSetData after called with found values");
15093  }
15094  if(ForwardCount > 1)
15095  {
15096  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + OtherHeadCode + " from a train whose headcode is " +
15097  MainHeadCode);
15098  TrainDataVector.clear();
15099  Utilities->CallLogPop(836);
15100  return(false);
15101  }
15102  // reverse check
15103  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
15104  {
15105  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
15106  if(TDEntry.HeadCode == OtherHeadCode)
15107  {
15108  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
15109  {
15110  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
15111  if(!Shuttle && (AVEntry.Command != "Sns-sh") && (AVEntry.Command != "Snt-sh") && (AVEntry.Command != "Fns-sh") && (AVEntry.Command != "Frh-sh"))
15112  {
15113  if(AVEntry.OtherHeadCode == MainHeadCode)
15114  {
15115  OtherTrainDataPtr = &TrainDataVector.at(x);
15116  ReverseCount++;
15117  ReverseEntryPtr = &AVEntry;
15118  ReverseTDVectorNumber = x;
15119  }
15120  }
15121  else if(Shuttle && ((AVEntry.Command == "Sns-sh") || (AVEntry.Command == "Snt-sh") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh")))
15122  {
15123  if(AVEntry.OtherHeadCode == MainHeadCode)
15124  {
15125  OtherTrainDataPtr = &TrainDataVector.at(x);
15126  ReverseCount++;
15127  ReverseEntryPtr = &AVEntry;
15128  ReverseTDVectorNumber = x;
15129  }
15130  }
15131  }
15132  }
15133  }
15134 
15135  if(ReverseCount == 0)
15136  {
15137  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + OtherHeadCode);
15138  TrainDataVector.clear();
15139  Utilities->CallLogPop(837);
15140  return(false);
15141  }
15142  if(ReverseCount > 1)
15143  {
15144  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
15145  OtherHeadCode);
15146  TrainDataVector.clear();
15147  Utilities->CallLogPop(838);
15148  return(false);
15149  }
15150  // these will all be false for !Shuttle
15151  bool ForwardShuttleStart = ((ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"));
15152  bool ForwardShuttleFinish = ((ForwardEntryPtr->Command == "Fns-sh") || (ForwardEntryPtr->Command == "Frh-sh"));
15153  bool ReverseShuttleStart = ((ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"));
15154  bool ReverseShuttleFinish = ((ReverseEntryPtr->Command == "Fns-sh") || (ReverseEntryPtr->Command == "Frh-sh"));
15155 
15156  if(Shuttle && MainTrainDataPtr->ActionVector.back().FormatType != Repeat)
15157  {
15158  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat");
15159  TrainDataVector.clear();
15160  Utilities->CallLogPop(1058);
15161  return(false);
15162  }
15163  if(Shuttle && OtherTrainDataPtr->ActionVector.back().FormatType != Repeat)
15164  {
15165  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + OtherHeadCode + " does not have a repeat");
15166  TrainDataVector.clear();
15167  Utilities->CallLogPop(1059);
15168  return(false);
15169  }
15170  if(SetDataAndCheckLocations)
15171  {
15172  if(ForwardEntryPtr->LocationName == "")
15173  {
15174  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
15175  ". One or other service does not have a location set");
15176  TrainDataVector.clear();
15177  Utilities->CallLogPop(526);
15178  return(false);
15179  }
15180  if(ReverseEntryPtr->LocationName == "")
15181  {
15182  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + OtherHeadCode +
15183  ". One or other service does not have a location set");
15184  TrainDataVector.clear();
15185  Utilities->CallLogPop(527);
15186  return(false);
15187  }
15188  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
15189  {
15190  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
15191  " is at a different location to the referencing train " + MainHeadCode);
15192  TrainDataVector.clear();
15193  Utilities->CallLogPop(842);
15194  return(false);
15195  }
15196  }
15197  // ignore shuttle repeat links for first time check
15198  if(!Shuttle)
15199  {
15200  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
15201  {
15202  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + OtherHeadCode +
15203  " has a different event time to the referencing train " + MainHeadCode);
15204  TrainDataVector.clear();
15205  Utilities->CallLogPop(525);
15206  return(false);
15207  }
15208  }
15209  // need to allow for repeat times multiplying up by repeating time for shuttle repeat links
15210  // no need to check from reverse to forward as already checked links consistent, and if include will send message twice
15211  if(ForwardShuttleStart && ReverseShuttleFinish)
15212  // Shuttle must be true if these are true
15213  {
15214  if(!CheckShuttleRepeatTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime, OtherTrainDataPtr->ActionVector.back().RearStartOrRepeatMins))
15215  {
15216  SecondPassMessage(GiveMessages, "Error in timetable - shuttle service " + MainHeadCode +
15217  " first repeat restart time not consistent with finish service " + OtherHeadCode);
15218  TrainDataVector.clear();
15219  Utilities->CallLogPop(1055);
15220  return(false);
15221  }
15222  }
15223  if((ReverseEntryPtr->Command == "Sfs") || (ReverseEntryPtr->Command == "Sns"))
15224  // doesn't matter about ForwardEntryPtr being Sfs/Sns as called for every occurrence of an 'OtherHeadCode' so won't escape
15225  {
15226  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15227  {
15228  SecondPassMessage(GiveMessages, "Error in timetable - an 'Sfs' or 'Sns' event (" + OtherHeadCode +
15229  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15230  TrainDataVector.clear();
15231  Utilities->CallLogPop(528);
15232  return(false);
15233  }
15234  }
15235  if(ReverseEntryPtr->Command == "Fjo")
15236  // doesn't matter about ForwardEntryPtr being Fjo as called for every occurrence of an 'OtherHeadCode' so won't escape
15237  {
15238  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15239  {
15240  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fjo' event (" + OtherHeadCode +
15241  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15242  TrainDataVector.clear();
15243  Utilities->CallLogPop(862);
15244  return(false);
15245  }
15246  }
15247  if(ReverseEntryPtr->Command == "Fns")
15248  // doesn't matter about ForwardEntryPtr being Fns as called for every occurrence of an 'OtherHeadCode' so won't escape
15249  {
15250  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
15251  {
15252  SecondPassMessage(GiveMessages, "Error in timetable - an 'Fns' event (" + OtherHeadCode +
15253  ") appears in the same sequence as the corresponding linked event " + MainHeadCode);
15254  TrainDataVector.clear();
15255  Utilities->CallLogPop(529);
15256  return(false);
15257  }
15258  }
15259  if(ForwardEntryPtr->Command == "Sfs")
15260  {
15261  if((ReverseEntryPtr->Command != "fsp") && (ReverseEntryPtr->Command != "rsp"))
15262  {
15263  SecondPassMessage(GiveMessages,
15264  "Error in timetable - unable to find a corresponding split train event for the train that starts from a split whose headcode is " +
15265  MainHeadCode);
15266  TrainDataVector.clear();
15267  Utilities->CallLogPop(530);
15268  return(false);
15269  }
15270  }
15271  if((ForwardEntryPtr->Command == "fsp") || (ForwardEntryPtr->Command == "rsp"))
15272  {
15273  if(ReverseEntryPtr->Command != "Sfs")
15274  {
15275  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sfs' event for the train split whose headcode is " +
15276  MainHeadCode);
15277  TrainDataVector.clear();
15278  Utilities->CallLogPop(839);
15279  return(false);
15280  }
15281  else
15282  {
15283  if(SetDataAndCheckLocations)
15284  {
15285  if(!(Track->TimetabledLocationNameAllocated(4, ForwardEntryPtr->LocationName)))
15286  {
15287  SecondPassMessage(GiveMessages, "Error in timetable - can't find timetabled location '" + ForwardEntryPtr->LocationName + "' in railway - perhaps there are concourses without platforms?");
15288  TrainDataVector.clear();
15289  Utilities->CallLogPop(849);
15290  return(false);
15291  }
15292  if(!(Track->OneNamedLocationElementAtLocation(0, ForwardEntryPtr->LocationName)))
15293  {
15294  SecondPassMessage(GiveMessages, "Error in timetable - can't find any named location elements at '" + ForwardEntryPtr->LocationName + "' - perhaps there are concourses without platforms?");
15295  TrainDataVector.clear();
15296  Utilities->CallLogPop(850);
15297  return(false);
15298  }
15299 //determine whether LocationName is a station or non-station
15300  bool StationLocation = false;
15301  for(TTrack::TTrackVectorIterator TEIt = Track->InactiveTrackVector.begin(); TEIt != Track->InactiveTrackVector.end(); TEIt++)
15302  {
15303  if(TEIt->LocationName == ForwardEntryPtr->LocationName)
15304  {
15305  if(TEIt->TrackType != NamedNonStationLocation)
15306  {
15307  StationLocation = true;
15308  }
15309  }
15310  }
15311  if(StationLocation)
15312  {
15313  if(!(Track->OneStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
15314  {
15315  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
15316  TrainDataVector.clear();
15317  Utilities->CallLogPop(846);
15318  return(false);
15319  }
15320  }
15321  else
15322  {
15323  if(!(Track->OneNonStationLongEnoughForSplit(0, ForwardEntryPtr->LocationName)))
15324  {
15325  SecondPassMessage(GiveMessages, "Error in timetable - location too short to split a train at " + ForwardEntryPtr->LocationName);
15326  TrainDataVector.clear();
15327  Utilities->CallLogPop(2660);
15328  return(false);
15329  }
15330  }
15331  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15332  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15333  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
15334  {
15335  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
15336  }
15337  }
15338  // NB: May not be set if main train is a service continuation without a description, if so can't do much about it but doesn't affect operation, just the train information display
15339  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
15340  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15341  }
15342  }
15343  if(ForwardEntryPtr->Command == "Sns")
15344  {
15345  if(ReverseEntryPtr->Command != "Fns")
15346  {
15347  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fns' event for the 'Sns' train whose headcode is " +
15348  MainHeadCode + " and is formed from a service with headcode " + OtherHeadCode);
15349  TrainDataVector.clear();
15350  Utilities->CallLogPop(531);
15351  return(false);
15352  }
15353  }
15354  if(ForwardEntryPtr->Command == "Fns")
15355  {
15356  if(ReverseEntryPtr->Command != "Sns")
15357  {
15358  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns' event for the train whose headcode is " + MainHeadCode +
15359  " and forms a new service with headcode " + OtherHeadCode);
15360  TrainDataVector.clear();
15361  Utilities->CallLogPop(840);
15362  return(false);
15363  }
15364  else
15365  {
15366  if(SetDataAndCheckLocations)
15367  {
15368  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15369  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15370  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
15371  {
15372  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
15373  }
15374  }
15375  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15376  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
15377  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
15378  }
15379  }
15380  if(ForwardEntryPtr->Command == "jbo")
15381  {
15382  if(ReverseEntryPtr->Command != "Fjo")
15383  {
15384  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Fjo' event for the train whose headcode is " + MainHeadCode +
15385  " and is joined by a train with headcode " + OtherHeadCode);
15386  TrainDataVector.clear();
15387  Utilities->CallLogPop(841);
15388  return(false);
15389  }
15390  }
15391  if(ForwardEntryPtr->Command == "Fjo")
15392  {
15393  if(ReverseEntryPtr->Command != "jbo")
15394  {
15395  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'jbo' event for the train whose headcode is " + MainHeadCode +
15396  " and joins a train with headcode " + OtherHeadCode);
15397  TrainDataVector.clear();
15398  Utilities->CallLogPop(532);
15399  return(false);
15400  }
15401  else
15402  {
15403  if(SetDataAndCheckLocations)
15404  {
15405  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15406  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15407  }
15408 /*
15409  if((MainTrainDataPtr->MaxRunningSpeed > 5) && (MainTrainDataPtr->MaxRunningSpeed < OtherTrainDataPtr->MaxRunningSpeed))
15410  {
15411  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed; //this should only be set when the trains join
15412  } not when internal timetable being compiled. Dropped at v2.15.0 when Brent Mackie reported error on 18/02/23 via discord
15413 
15414  //added test for > 5 [5 used instead of 0 because of possible floating point errors - though unlikely] above at v1.3.1 because the train will have a
15415  //zero MaxRunningSpeed if it continues from another service - its max speed is set when it takes over from the other service
15416  //notified of this problem by Ian Walker in his email of 25/03/13. Probably redundant anyway because the max speed is reduced at the changeover if the
15417  //'joined by' train's max speed is less.
15418 */
15419  }
15420  }
15421  if(ForwardShuttleStart)
15422  // (ForwardEntryPtr->Command == "Sns-sh") || (ForwardEntryPtr->Command == "Snt-sh"))
15423  {
15424  if(!ReverseShuttleFinish)
15425  // (ReverseEntryPtr->Command != "Fns-sh") && (ReverseEntryPtr->Command != "Frh-sh"))
15426  {
15427  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + MainHeadCode +
15428  " from train whose headcode is " + OtherHeadCode + ", has to be Fns-sh, Frh-sh");
15429  TrainDataVector.clear();
15430  Utilities->CallLogPop(1056);
15431  return(false);
15432  }
15433  }
15434  if(ReverseShuttleStart)
15435  // (ReverseEntryPtr->Command == "Sns-sh") || (ReverseEntryPtr->Command == "Snt-sh"))
15436  {
15437  if(!ForwardShuttleFinish)
15438  // (ForwardEntryPtr->Command != "Fns-sh") && (ForwardEntryPtr->Command != "Frh-sh"))
15439  {
15440  SecondPassMessage(GiveMessages, "Error in timetable - incorrect shuttle link to train whose headcode is " + OtherHeadCode +
15441  " from train whose headcode is " + MainHeadCode + ", has to be Fns-sh, Frh-sh");
15442  TrainDataVector.clear();
15443  Utilities->CallLogPop(1057);
15444  return(false);
15445  }
15446  else
15447  {
15448  if(SetDataAndCheckLocations)
15449  {
15450  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
15451  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
15452  }
15453 /* don't need LinkedTrainEntryPtr for 'OtherTrain' & don't need data transfer as this is done in the
15454  non-repeating link for Sns-sh & is provided at the outset for Snt-sh
15455 */
15456  }
15457  }
15458  // check repeat information consistent if present
15459  // note that won't be affected by the non-repeating shuttle links as these are in NonRepeatingShuttleLinkHeadCode
15460  // and those not accessed here
15461 
15462  // still need to check the non-repeating links and that they have no repeats - do that outside this function
15463  bool MainRepeat = false, OtherRepeat = false;
15464  TActionVectorEntry MainRepeatEntry, OtherRepeatEntry;
15465 
15466  if(MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
15467  {
15468  MainRepeat = true;
15469  MainRepeatEntry = MainTrainDataPtr->ActionVector.at(MainTrainDataPtr->ActionVector.size() - 1);
15470  }
15471  if(OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1).FormatType == Repeat)
15472  {
15473  OtherRepeat = true;
15474  OtherRepeatEntry = OtherTrainDataPtr->ActionVector.at(OtherTrainDataPtr->ActionVector.size() - 1);
15475  }
15476  if((MainRepeat && !OtherRepeat) || (!MainRepeat && OtherRepeat))
15477  {
15478  SecondPassMessage(GiveMessages, "Error in timetable - only one repeat is provided for the train whose headcode is " + MainHeadCode +
15479  " and the associated train with headcode " + OtherHeadCode);
15480  TrainDataVector.clear();
15481  Utilities->CallLogPop(844);
15482  return(false);
15483  }
15484  if(MainRepeat && OtherRepeat)
15485  {
15486  if((MainRepeatEntry.EventTime != OtherRepeatEntry.EventTime) || (MainRepeatEntry.RearStartOrRepeatMins != OtherRepeatEntry.RearStartOrRepeatMins) ||
15487  (MainRepeatEntry.FrontStartOrRepeatDigits != OtherRepeatEntry.FrontStartOrRepeatDigits) ||
15488  (MainRepeatEntry.NumberOfRepeats != OtherRepeatEntry.NumberOfRepeats))
15489  {
15490  SecondPassMessage(GiveMessages, "Error in timetable - repeat items don't correspond for the train whose headcode is " + MainHeadCode +
15491  " and the associated train with headcode " + OtherHeadCode);
15492  TrainDataVector.clear();
15493  Utilities->CallLogPop(845);
15494  return(false);
15495  }
15496  }
15497  Utilities->CallLogPop(863);
15498  return(true);
15499 }
15500 
15501 // ---------------------------------------------------------------------------
15502 
15503 void TTrainController::StripSpaces(int Caller, AnsiString &Input)
15504 // strip both leading and trailing spaces at ends of text and spaces before and after all commas and semicolons within the text
15505 {
15506  // strip spaces from extreme ends of input
15507  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StripSpaces," + AnsiString(Input));
15508  if(Input == "")
15509  {
15510  Utilities->CallLogPop(856);
15511  return;
15512  }
15513  while(Input[1] == ' ')
15514  {
15515  if(Input.Length() > 1)
15516  {
15517  Input = Input.SubString(2, Input.Length() - 1);
15518  }
15519  else
15520  {
15521  Input = "";
15522  Utilities->CallLogPop(857);
15523  return;
15524  }
15525  }
15526  if(Input == "")
15527  {
15528  Utilities->CallLogPop(858);
15529  return;
15530  }
15531  while(Input[Input.Length()] == ' ')
15532  {
15533  if(Input.Length() > 1)
15534  {
15535  Input = Input.SubString(1, Input.Length() - 1);
15536  }
15537  else
15538  {
15539  Input = "";
15540  Utilities->CallLogPop(859);
15541  return;
15542  }
15543  }
15544  // now strip spaces immediately after all commas and semicolons within the text
15545  AnsiString Output = "";
15546  bool DelimiterFound = false;
15547 
15548  for(int x = 1; x < Input.Length() + 1; x++)
15549  {
15550  if(DelimiterFound)
15551  {
15552  if(Input[x] == ' ')
15553  {
15554  continue;
15555  }
15556  }
15557  if((Input[x] != ',') && (Input[x] != ';'))
15558  {
15559  DelimiterFound = false;
15560  Output = Output + Input[x];
15561  }
15562  else
15563  {
15564  DelimiterFound = true;
15565  Output = Output + Input[x];
15566  }
15567  }
15568  if(Output == "")
15569  {
15570  Input = "";
15571  Utilities->CallLogPop(860);
15572  return;
15573  }
15574  // now strip spaces immediately before all commas and semicolons within the text
15575  Input = Output;
15576  Output = "";
15577  DelimiterFound = false;
15578  for(int x = Input.Length(); x > 0; x--)
15579  {
15580  if(DelimiterFound)
15581  {
15582  if(Input[x] == ' ')
15583  {
15584  continue;
15585  }
15586  }
15587  if((Input[x] != ',') && (Input[x] != ';'))
15588  {
15589  DelimiterFound = false;
15590  Output = AnsiString(Input[x]) + Output;
15591  }
15592  else
15593  {
15594  DelimiterFound = true;
15595  Output = AnsiString(Input[x]) + Output;
15596  }
15597  }
15598  Input = Output;
15599  Utilities->CallLogPop(861);
15600 }
15601 
15602 // ---------------------------------------------------------------------------
15603 
15604 bool TTrainController::IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName) //this is the original version re-instated at v2.15.0 Beta6
15605 // checks if an Snt or Snt-sh entry with zero starting speed is followed (somewhere, not necessarily immediately) by a TimeLoc & has the same LocationName
15606 // and if so returns true. Also returns true for Snt, not Snt-sh, if at least 1 start element is a location & the entry is either
15607 // a signaller control entry & speed is zero or it is followed immediately by Frh, Fjo, Fns or F-nshs (allows empty stock pickup).
15608 // Always return false for entry at a continuation (may be named but not a stop location). Note that no successor validity checks
15609 // are done in this function, they must be done elsewhere.
15610 //a starting speed > 0 always returns false
15611 {
15612  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsSNTEntryLocated," + AnsiString(TDEntry.HeadCode));
15613  const TActionVectorEntry &AVEntry0 = TDEntry.ActionVector.at(0);
15614  LocationName = "";
15615  if(TDEntry.StartSpeed > 0)
15616  {
15617  Utilities->CallLogPop(1784);
15618  return(false);
15619  }
15620  if((AVEntry0.Command != "Snt") && (AVEntry0.Command != "Snt-sh"))
15621  {
15622  throw Exception("Error, first event not 'Snt' or 'Snt-sh' in IsSNTEntryLocated");
15623  }
15625  {
15626  Utilities->CallLogPop(852);
15627  return(false);
15628  }
15629  AnsiString LocRear = Track->TrackElementAt(507, AVEntry0.RearStartOrRepeatMins).ActiveTrackElementName;
15630  AnsiString LocFront = Track->TrackElementAt(508, AVEntry0.FrontStartOrRepeatDigits).ActiveTrackElementName;
15631 
15632  if(LocRear != "")
15633  {
15634  LocationName = LocRear;
15635  }
15636  else
15637  {
15638  LocationName = LocFront;
15639  }
15640  if(LocationName == "")
15641  {
15642  Utilities->CallLogPop(1036);
15643  return(false);
15644  }
15645  if(AVEntry0.SignallerControl)
15646  {
15647  Utilities->CallLogPop(1773);
15648  return(true);
15649  }
15650 // here if not a signaller start entry so must be at least one more entry, and it must be at the same location as the Snt to be located
15651 
15652 //Ok Not ok continue
15653 
15654 //Frh if Snt Frh-sh cdt
15655 //Fns if Snt Fns-sh fsp or rsp
15656 //Fjo if Snt TimeTimeLoc jbo
15657 //F-nshs if Snt pas dsc
15658 //TimeLoc dep Fer
15659 // TimeLoc arr
15660 
15661  for(unsigned int y = 0; y < TDEntry.ActionVector.size(); y++)
15662  {
15663  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
15664  if(((AVEntry.Command == "Frh") || (AVEntry.Command == "Fjo") || (AVEntry.Command == "F-nshs") || (AVEntry.Command == "Fns")) && (AVEntry0.Command == "Snt")) // added Fjo at v2.0.0 for empty stock
15665  {
15666  Utilities->CallLogPop(1037);
15667  return(true);
15668  }
15669  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName == LocationName)) //will be a departure if same name- times not set yet so can't use them to confirm
15670  {
15671  Utilities->CallLogPop(2442);
15672  return(true);
15673  }
15674  if((AVEntry.FormatType == TimeLoc) && (AVEntry.LocationName != LocationName)) //arrival, not located
15675  {
15676  Utilities->CallLogPop(2438);
15677  return(false);
15678  }
15679  if((AVEntry.Command == "Fer") || (AVEntry.Command == "pas") || (AVEntry.Command == "Fns-sh") || (AVEntry.Command == "Frh-sh") || (AVEntry.FormatType == TimeTimeLoc))
15680  {
15681  Utilities->CallLogPop(854);
15682  return(false);
15683  }
15684  if((AVEntry.Command == "cdt") || (AVEntry.Command == "dsc") || (AVEntry.Command == "fsp") || (AVEntry.Command == "rsp") || (AVEntry.Command == "jbo"))
15685  {
15686  continue;
15687  }
15688  }
15689  Utilities->CallLogPop(855);
15690  return(false);
15691 
15692 }
15693 
15694 // ---------------------------------------------------------------------------
15695 
15696 bool TTrainController::CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
15697 {
15698  // checks that the new train start elements are valid - both exist & are connected, and that not
15699  // attempting to start on a diverging leg (i.e. one segment on points & other on element connected to diverging leg) <--dropped at v2.18.0
15700  // & not starting with front on a continuation
15701  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartPositionValidity," + RearElementStr + "," + FrontElementStr);
15702  int RearPosition = 0, FrontPosition = 0; // RearExitPos = 0; //dropped at v2.18.0
15703 
15704  RearPosition = Track->GetTrackVectorPositionFromString(5, RearElementStr, GiveMessages);
15705  if(RearPosition < 0)
15706  // error message given in GetTrackVectorPositionFromString
15707  {
15708  Utilities->CallLogPop(759);
15709  return(false);
15710  }
15711  FrontPosition = Track->GetTrackVectorPositionFromString(6, FrontElementStr, GiveMessages);
15712  if(FrontPosition < 0)
15713  // error message given in GetTrackVectorPositionFromString
15714  {
15715  Utilities->CallLogPop(760);
15716  return(false);
15717  }
15718  TTrackElement RearTrackElement = Track->TrackElementAt(490, RearPosition);
15719  TTrackElement FrontTrackElement = Track->TrackElementAt(491, FrontPosition);
15720 // TTrackType RearType = RearTrackElement.TrackType; //dropped at v2.18.0
15721  TTrackType FrontType = FrontTrackElement.TrackType;
15722 
15723  // check front & rear connected
15724  for(int x = 0; x < 4; x++)
15725  {
15726  if(RearTrackElement.Conn[x] == FrontPosition)
15727  {
15728 // RearExitPos = x; //dropped at v2.18.0
15729  break;
15730  }
15731  if(x == 3) //if it gets here & not already found then not connected
15732  {
15733  TimetableMessage(GiveMessages, "Front element: " + FrontTrackElement.ElementID + " not linked to rear element: " + RearTrackElement.ElementID);
15734  Utilities->CallLogPop(762);
15735  return(false);
15736  }
15737  }
15738  // check not starting with front on a continuation
15739  if(FrontType == Continuation)
15740  {
15741  TimetableMessage(GiveMessages, "Front of train attempting to start on a continuation at: " + FrontElementStr);
15742  Utilities->CallLogPop(937);
15743  return(false);
15744  }
15745  // check not starting on a level crossing
15746  if(Track->IsLCAtHV(43, FrontTrackElement.HLoc, FrontTrackElement.VLoc))
15747  {
15748  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + FrontElementStr);
15749  Utilities->CallLogPop(1951);
15750  return(false);
15751  }
15752  if(Track->IsLCAtHV(44, RearTrackElement.HLoc, RearTrackElement.VLoc))
15753  {
15754  TimetableMessage(GiveMessages, "Train attempting to start on a level crossing at: " + RearElementStr);
15755  Utilities->CallLogPop(1952);
15756  return(false);
15757  }
15758  // check if trying to start on diverging leg of points - allowed at v2.18.0, checks done during operation
15759 /*
15760  if((RearType == Points) && (RearExitPos == 3))
15761  {
15762  TimetableMessage(GiveMessages, "Front of train attempting to start on element connected to diverging points at: " + RearElementStr);
15763  Utilities->CallLogPop(936);
15764  return(false);
15765  }
15766 
15767  if((FrontType == Points) && (RearTrackElement.ConnLinkPos[RearExitPos] == 3))
15768  {
15769  TimetableMessage(GiveMessages, "Rear of train attempting to start on element connected to diverging points at: " + FrontElementStr);
15770  Utilities->CallLogPop(1808);
15771  return(false);
15772  }
15773 */
15774  Utilities->CallLogPop(905);
15775  return(true);
15776 }
15777 
15778 // ---------------------------------------------------------------------------
15779 
15780 bool TTrainController::CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
15781 // Rear & front element validity already checked in CheckStartPositionValidity
15782 // This checks for points in correct orientation, no train at start position and not starting on a locked route
15783 {
15784  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckStartAllowable," + AnsiString(RearPosition) + "," +
15785  AnsiString(RearExitPos));
15786  TTrackElement RearTrackElement = Track->TrackElementAt(517, RearPosition);
15787 
15788  if(RearTrackElement.TrackType == Continuation)
15789  {
15790  EventType = FailTrainEntry;
15791  }
15792  else
15793  {
15794  EventType = FailCreateTrain;
15795  }
15796  int FrontPosition = RearTrackElement.Conn[RearExitPos];
15797  TTrackElement FrontTrackElement = Track->TrackElementAt(798, FrontPosition);
15798  int FrontEntryPos = RearTrackElement.ConnLinkPos[RearExitPos];
15799  TTrackType RearType = RearTrackElement.TrackType;
15800  TTrackType FrontType = FrontTrackElement.TrackType;
15801  AnsiString RearName, FrontName;
15802 
15803  if(RearTrackElement.ActiveTrackElementName != "")
15804  {
15805  RearName = RearTrackElement.ActiveTrackElementName;
15806  }
15807  else
15808  {
15809  RearName = RearTrackElement.ElementID;
15810  }
15811  if(FrontTrackElement.ActiveTrackElementName != "")
15812  {
15813  FrontName = FrontTrackElement.ActiveTrackElementName;
15814  }
15815  else
15816  {
15817  FrontName = FrontTrackElement.ElementID;
15818  }
15819  TPrefDirElement PrefDirElement; // needed for next function but not used
15820  int LockedVectorNumber; // needed for next function but not used
15821 
15822  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(12, FrontPosition, FrontEntryPos, PrefDirElement, LockedVectorNumber))
15823  {
15824  if(ReportFlag)
15825  {
15826  if(EventType == FailCreateTrain)
15827  {
15828  EventType = FailCreateLockedRoute;
15829  }
15830  else
15831  {
15832  EventType = FailEnterLockedRoute;
15833  }
15834  LogActionError(47, HeadCode, "", EventType, FrontName);
15835  }
15836  Utilities->CallLogPop(940);
15837  return(false);
15838  }
15839  if(AllRoutes->IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(13, RearPosition, RearExitPos, PrefDirElement, LockedVectorNumber))
15840  {
15841  if(ReportFlag)
15842  {
15843  if(EventType == FailCreateTrain)
15844  {
15845  EventType = FailCreateLockedRoute;
15846  }
15847  else
15848  {
15849  EventType = FailEnterLockedRoute;
15850  }
15851  LogActionError(48, HeadCode, "", EventType, RearName);
15852  }
15853  Utilities->CallLogPop(1809);
15854  return(false);
15855  }
15856  if((RearType != Bridge) && (RearTrackElement.TrainIDOnElement > -1))
15857  {
15858  if(ReportFlag)
15859  {
15860  LogActionError(27, HeadCode, "", EventType, RearName);
15861  }
15862  Utilities->CallLogPop(1810);
15863  return(false);
15864  }
15865  if((FrontType != Bridge) && (FrontTrackElement.TrainIDOnElement > -1))
15866  {
15867  if(ReportFlag)
15868  {
15869  if(EventType == FailCreateTrain)
15870  {
15871  LogActionError(28, HeadCode, "", EventType, FrontName);
15872  }
15873  else
15874  {
15875  LogActionError(43, HeadCode, "", EventType, RearName);
15876  }
15877  }
15878  Utilities->CallLogPop(941);
15879  return(false);
15880  }
15881  if(RearType == Bridge)
15882  {
15883  if((RearExitPos > 1) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
15884  {
15885  if(ReportFlag)
15886  {
15887  LogActionError(29, HeadCode, "", EventType, RearName);
15888  }
15889  Utilities->CallLogPop(942);
15890  return(false);
15891  }
15892  if((RearExitPos < 2) && (RearTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
15893  {
15894  if(ReportFlag)
15895  {
15896  LogActionError(30, HeadCode, "", EventType, RearName);
15897  }
15898  Utilities->CallLogPop(943);
15899  return(false);
15900  }
15901  }
15902  if(FrontType == Bridge)
15903  {
15904  if((FrontEntryPos > 1) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit23 > -1))
15905  {
15906  if(ReportFlag)
15907  {
15908  if(EventType == FailCreateTrain)
15909  {
15910  LogActionError(31, HeadCode, "", EventType, FrontName);
15911  }
15912  else
15913  {
15914  LogActionError(44, HeadCode, "", EventType, RearName);
15915  }
15916  }
15917  Utilities->CallLogPop(944);
15918  return(false);
15919  }
15920  if((FrontEntryPos < 2) && (FrontTrackElement.TrainIDOnBridgeOrFailedPointOrigSpeedLimit01 > -1))
15921  {
15922  if(ReportFlag)
15923  {
15924  if(EventType == FailCreateTrain)
15925  {
15926  LogActionError(45, HeadCode, "", EventType, FrontName);
15927  }
15928  else
15929  {
15930  LogActionError(46, HeadCode, "", EventType, RearName);
15931  }
15932  }
15933  Utilities->CallLogPop(945);
15934  return(false);
15935  }
15936  }
15937  EventType = FailCreatePoints; //modified at v2.18.0 so only fails if starting positions conflict with point attribute
15938  if(RearType == Points)
15939  {
15940  if(((RearTrackElement.Attribute == 1) && (RearExitPos == 1)) || ((RearTrackElement.Attribute == 0) && (RearExitPos == 3)))
15941  {
15942  if(ReportFlag)
15943  {
15944  LogActionError(33, HeadCode, "", FailCreatePoints, RearName);
15945  StopTTClockMessage(157, HeadCode + " can't be created, points set wrongly at " + RearName);
15946  }
15947  Utilities->CallLogPop(933);
15948  return(false);
15949  }
15950  }
15951  if(FrontType == Points)
15952  {
15953  if(((FrontTrackElement.Attribute == 1) && (FrontEntryPos == 1)) || ((FrontTrackElement.Attribute == 0) && (FrontEntryPos == 3)))
15954  {
15955  if(ReportFlag)
15956  {
15957  LogActionError(34, HeadCode, "", FailCreatePoints, FrontName);
15958  StopTTClockMessage(158, HeadCode + " can't be created, points set wrongly at " + RearName);
15959  }
15960  Utilities->CallLogPop(934);
15961  return(false);
15962  }
15963  }
15964 
15965  //this section added at v2.9.1 to prevent entry for a train when there's a route set against it
15966  int HLoc = Track->TrackElementAt(1027, RearPosition).HLoc;
15967  int VLoc = Track->TrackElementAt(1028, RearPosition).VLoc;
15968  int ELink = Track->TrackElementAt(1029, RearPosition).Link[RearExitPos]; //if route entry corresponds to RearExitPos then it's set against the train
15969  int RouteNumber; //not used
15970  if(Track->TrackElementAt(1030, RearPosition).TrackType == Continuation)
15971  {
15972  if(AllRoutes->FindRouteNumberFromRoute2MultiMapNoErrors(8, HLoc, VLoc, ELink, RouteNumber))
15973  {
15974  EventType = FailEntryRouteSetAgainst;
15975  if(ReportFlag)
15976  {
15977  LogActionError(63, HeadCode, "", EventType, RearName);
15978  }
15979  Utilities->CallLogPop(2317);
15980  return(false);
15981  }
15982  }
15983  Utilities->CallLogPop(939);
15984  return(true);
15985 }
15986 
15987 // ---------------------------------------------------------------------------
15988 
15989 AnsiString TTrainController::GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
15990 {
15991  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatHeadCode," + BaseHeadCode + "," + AnsiString(RepeatNumber) +
15992  "," + AnsiString(IncDigits));
15993  if(!Last2CharactersBothDigits(1, BaseHeadCode) && (IncDigits > 0))
15994  {
15995  throw Exception("Error, last 2 characters not both digits and IncDigits > 0 in GetRepeatHeadCode");
15996  }
15997  if(!Last2CharactersBothDigits(2, BaseHeadCode))
15998  {
15999  Utilities->CallLogPop(1893);
16000  return(BaseHeadCode);
16001  }
16002  int BaseDigits = BaseHeadCode.SubString(3, 2).ToInt();
16003  int NextRepeatDigits = BaseDigits + (IncDigits * RepeatNumber);
16004 
16005  while(NextRepeatDigits >= 100)
16006  {
16007  NextRepeatDigits -= 100; // rolls over after 99
16008  }
16009  AnsiString NextRepeatDigitsStr = AnsiString(NextRepeatDigits);
16010 
16011  if(NextRepeatDigitsStr.Length() < 2)
16012  {
16013  NextRepeatDigitsStr = AnsiString('0') + NextRepeatDigitsStr;
16014  }
16015  AnsiString NextRepeatHeadCode = BaseHeadCode.SubString(1, 2) + NextRepeatDigitsStr;
16016 
16017  Utilities->CallLogPop(1365);
16018  return(NextRepeatHeadCode);
16019 }
16020 
16021 // ---------------------------------------------------------------------------
16022 
16023 TDateTime TTrainController::GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
16024 {
16025  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetRepeatTime," + AnsiString(double(BasicTime)) + "," +
16026  AnsiString(RepeatNumber) + "," + AnsiString(IncMinutes));
16027  TDateTime NextRepeatTime = BasicTime + TDateTime(((double)(RepeatNumber * IncMinutes)) / 1440.0); // 1440 = no. of minutes in 24h
16028  Utilities->CallLogPop(1366);
16029  return(NextRepeatTime);
16030 }
16031 
16032 // ---------------------------------------------------------------------------
16033 
16034 bool TTrainController::CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
16035 // For success the ForwardEventTime + repeat time should == ReverseEventTime (allow 10secs either way since converting to doubles)
16036 {
16037  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleRepeatTime," + AnsiString(double(ForwardEventTime)) + "," +
16038  AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes));
16039  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16040  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16041  int RepeatSecs = RepeatMinutes * 60;
16042 
16043  if((ForwardSecs > (ReverseSecs - RepeatSecs + 10)) || (ForwardSecs < (ReverseSecs - RepeatSecs - 10)))
16044  {
16045  Utilities->CallLogPop(1367);
16046  return(false);
16047  }
16048  else
16049  {
16050  Utilities->CallLogPop(1368);
16051  return(true);
16052  }
16053 }
16054 
16055 // ---------------------------------------------------------------------------
16056 
16057 bool TTrainController::CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
16058 // check for proper non-repeating link cross references and that they have no repeats & that times are consistent, set links if SetDataAndCheckLocations true
16059 
16060 /* Double crosslink (shuttle) table:
16061 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
16062  Code ShuttleLink- EntryPtr ShuttleLink-
16063  HeadCode EntryPtr
16064 
16065 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
16066 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
16067 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
16068 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
16069 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
16070 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
16071 
16072 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
16073 */
16074 
16075 {
16076  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinksAndSetData," + MainHeadCode + "," +
16077  NonRepeatingHeadCode);
16078  int ForwardCount = 0;
16079  int ReverseCount = 0;
16080  unsigned int ForwardTDVectorNumber, ReverseTDVectorNumber;
16081  TActionVectorEntry *ReverseEntryPtr = 0, *ForwardEntryPtr = 0;
16082  // Forward corresponds to Main, Reverse to Other
16083  TTrainDataEntry *MainTrainDataPtr = 0;
16084  TTrainDataEntry *OtherTrainDataPtr = 0;
16085 
16086  // forward check
16087  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16088  {
16089  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16090  if(TDEntry.HeadCode == MainHeadCode)
16091  {
16092  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16093  {
16094  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16095  if(AVEntry.NonRepeatingShuttleLinkHeadCode == NonRepeatingHeadCode)
16096  {
16097  MainTrainDataPtr = &TrainDataVector.at(x);
16098  ForwardEntryPtr = &AVEntry;
16099  ForwardCount++;
16100  ForwardTDVectorNumber = x;
16101  }
16102  }
16103  }
16104  }
16105  if(ForwardCount == 0)
16106  // this is an exception because the headcodes are selected in the same order as the forward check
16107  {
16108  throw Exception("Error, ForwardCount == 0 in CheckNonRepeatingShuttleLinksAndSetData after called with found values");
16109  }
16110  if(ForwardCount > 1)
16111  {
16112  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + NonRepeatingHeadCode + " from a train whose headcode is " +
16113  MainHeadCode);
16114  TrainDataVector.clear();
16115  Utilities->CallLogPop(1061);
16116  return(false);
16117  }
16118  // reverse check
16119  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16120  {
16121  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16122  if(TDEntry.HeadCode == NonRepeatingHeadCode)
16123  {
16124  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16125  {
16126  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16127  if(AVEntry.NonRepeatingShuttleLinkHeadCode == MainHeadCode)
16128  {
16129  OtherTrainDataPtr = &TrainDataVector.at(x);
16130  ReverseCount++;
16131  ReverseEntryPtr = &AVEntry;
16132  ReverseTDVectorNumber = x;
16133  }
16134  }
16135  }
16136  }
16137 
16138  if(ReverseCount == 0)
16139  {
16140  SecondPassMessage(GiveMessages, "Error in timetable - cross reference missing in either " + MainHeadCode + " or " + NonRepeatingHeadCode);
16141  TrainDataVector.clear();
16142  Utilities->CallLogPop(1062);
16143  return(false);
16144  }
16145  if(ReverseCount > 1)
16146  {
16147  SecondPassMessage(GiveMessages, "Error in timetable - found more than one reference to " + MainHeadCode + " from a train whose headcode is " +
16148  NonRepeatingHeadCode);
16149  TrainDataVector.clear();
16150  Utilities->CallLogPop(1063);
16151  return(false);
16152  }
16153  if(((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh")) && (MainTrainDataPtr->ActionVector.back().FormatType == Repeat))
16154  {
16155  SecondPassMessage(GiveMessages, "Error in timetable - shuttle connecting train " + MainHeadCode + " shouldn't have a repeat");
16156  TrainDataVector.clear();
16157  Utilities->CallLogPop(1064);
16158  return(false);
16159  }
16160  if((ForwardEntryPtr->Command != "F-nshs") && (ForwardEntryPtr->Command != "Sns-fsh") && (MainTrainDataPtr->ActionVector.back().FormatType != Repeat))
16161  {
16162  SecondPassMessage(GiveMessages, "Error in timetable - shuttle train " + MainHeadCode + " does not have a repeat item");
16163  TrainDataVector.clear();
16164  Utilities->CallLogPop(1065);
16165  return(false);
16166  }
16167  if(SetDataAndCheckLocations)
16168  {
16169  if(ForwardEntryPtr->LocationName == "")
16170  {
16171  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
16172  ". One or other service does not have a location set");
16173  TrainDataVector.clear();
16174  Utilities->CallLogPop(1066);
16175  return(false);
16176  }
16177  if(ReverseEntryPtr->LocationName == "")
16178  {
16179  SecondPassMessage(GiveMessages, "Error in timetable - location error in cross referenced trains " + MainHeadCode + " and " + NonRepeatingHeadCode +
16180  ". One or other service does not have a location set");
16181  TrainDataVector.clear();
16182  Utilities->CallLogPop(1067);
16183  return(false);
16184  }
16185  if(ForwardEntryPtr->LocationName != ReverseEntryPtr->LocationName)
16186  {
16187  SecondPassMessage(GiveMessages, "Error in timetable - cross referenced train " + NonRepeatingHeadCode +
16188  " is at a different location to the referencing train " + MainHeadCode);
16189  TrainDataVector.clear();
16190  Utilities->CallLogPop(1068);
16191  return(false);
16192  }
16193  }
16194  if(ForwardEntryPtr->Command == "F-nshs")
16195  // i.e. the non repeating link into the shuttle service
16196  {
16197  if(ForwardEntryPtr->EventTime != ReverseEntryPtr->EventTime)
16198  {
16199  SecondPassMessage(GiveMessages, "Error in timetable - shuttle in-link service " + MainHeadCode +
16200  " finish time not consistent with start time of shuttle service " + NonRepeatingHeadCode);
16201  TrainDataVector.clear();
16202  Utilities->CallLogPop(1069);
16203  return(false);
16204  }
16205  }
16206  if(ForwardEntryPtr->Command == "Fns-sh")
16207  // i.e. the non repeating link out from the shuttle service
16208  {
16209  if(!CheckNonRepeatingShuttleLinkTime(0, ForwardEntryPtr->EventTime, ReverseEntryPtr->EventTime,
16210  MainTrainDataPtr->ActionVector.back().RearStartOrRepeatMins, MainTrainDataPtr->ActionVector.back().NumberOfRepeats))
16211  {
16212  SecondPassMessage(GiveMessages, "Error in timetable - service " + NonRepeatingHeadCode + ", which links out from shuttle service " + MainHeadCode +
16213  ", has the wrong start time. It should correspond to the finish time of the last shuttle.");
16214  TrainDataVector.clear();
16215  Utilities->CallLogPop(1070);
16216  return(false);
16217  }
16218  }
16219  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))
16220  // i.e. a non repeating link to or from the shuttle service
16221  {
16222  if(ReverseTDVectorNumber == ForwardTDVectorNumber)
16223  {
16224  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode +
16225  " appears in the same sequence as the corresponding shuttle service " + MainHeadCode);
16226  TrainDataVector.clear();
16227  Utilities->CallLogPop(1071);
16228  return(false);
16229  }
16230  }
16231 /* it's allowed to have a different description
16232  if((ForwardEntryPtr->Command == "F-nshs") || (ForwardEntryPtr->Command == "Sns-fsh"))//i.e. a non repeating link to or from the shuttle service
16233  {
16234  if((MainTrainDataPtr->Description != "") && (OtherTrainDataPtr->Description != "") && (MainTrainDataPtr->Description != OtherTrainDataPtr->Description))
16235  {
16236  SecondPassMessage(GiveMessages, "Error in timetable - the non repeating link service " + NonRepeatingHeadCode + " has a different description to the corresponding shuttle service " + MainHeadCode);
16237  TrainDataVector.clear();
16238  Utilities->CallLogPop(1072);
16239  return false;
16240  }
16241  }
16242 */
16243  if(ForwardEntryPtr->Command == "Sns-sh")
16244  {
16245  if(ReverseEntryPtr->Command != "F-nshs")
16246  {
16247  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'F-nshs' event for the 'Sns-sh' train whose headcode is " +
16248  MainHeadCode + " and is a new shuttle service formed from the service with headcode " + NonRepeatingHeadCode);
16249  TrainDataVector.clear();
16250  Utilities->CallLogPop(1073);
16251  return(false);
16252  }
16253  }
16254  if(ForwardEntryPtr->Command == "F-nshs")
16255  {
16256  if(ReverseEntryPtr->Command != "Sns-sh")
16257  {
16258  SecondPassMessage(GiveMessages, "Error in timetable - unable to find a corresponding 'Sns-sh' event for the 'F-nshs' train whose headcode is " +
16259  MainHeadCode + " and forms a new shuttle service with headcode " + NonRepeatingHeadCode);
16260  TrainDataVector.clear();
16261  Utilities->CallLogPop(1074);
16262  return(false);
16263  }
16264  else
16265  {
16266  if(SetDataAndCheckLocations)
16267  {
16268  ForwardEntryPtr->LinkedTrainEntryPtr = OtherTrainDataPtr;
16269  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16270  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16271  {
16272  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16273  }
16274  }
16275  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16276  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16277  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16278  }
16279  }
16280  if(ForwardEntryPtr->Command == "Sns-fsh")
16281  {
16282  if(ReverseEntryPtr->Command != "Fns-sh")
16283  {
16284  SecondPassMessage(GiveMessages,
16285  "Error in timetable - unable to find a corresponding 'Fns-sh' event for the 'Sns-fsh' non-shuttle service whose headcode is " + MainHeadCode +
16286  " formed from a shuttle service with headcode " + NonRepeatingHeadCode);
16287  TrainDataVector.clear();
16288  Utilities->CallLogPop(1075);
16289  return(false);
16290  }
16291  }
16292  if(ForwardEntryPtr->Command == "Fns-sh")
16293  {
16294  if(ReverseEntryPtr->Command != "Sns-fsh")
16295  {
16296  SecondPassMessage(GiveMessages,
16297  "Error in timetable - unable to find a corresponding 'Sns-fsh' event for the 'Fns-sh' shuttle service whose headcode is " + MainHeadCode +
16298  " and forms a new non-shuttle service with headcode " + NonRepeatingHeadCode);
16299  TrainDataVector.clear();
16300  Utilities->CallLogPop(1076);
16301  return(false);
16302  }
16303  else
16304  {
16305  if(SetDataAndCheckLocations)
16306  {
16307  ForwardEntryPtr->NonRepeatingShuttleLinkEntryPtr = OtherTrainDataPtr;
16308  // links to the non-repeating non-shuttle linked service
16309  ReverseEntryPtr->LinkedTrainEntryPtr = MainTrainDataPtr;
16310  // needed for creating formatted timetable
16311  if(OtherTrainDataPtr->FixedDescription == "") //name changed at v2.16.1
16312  {
16313  OtherTrainDataPtr->FixedDescription = MainTrainDataPtr->FixedDescription;
16314  }
16315  }
16316  // Probably redundant as this is continued from the earlier service when the changeover happens (also may not be set here yet if a service continuation)
16317  OtherTrainDataPtr->MaxRunningSpeed = MainTrainDataPtr->MaxRunningSpeed;
16318  }
16319  }
16320  Utilities->CallLogPop(1077);
16321  return(true);
16322 }
16323 
16324 // ---------------------------------------------------------------------------
16325 
16326 bool TTrainController::CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes, int RepeatNumber)
16327 // Forward train is the finish shuttle entry 'Fns-sh'.
16328 // The Reverse (new non-repeating service) time must == Forward time + (RepeatMins * RepeatNumber) but allow 10 secs either side
16329 {
16330  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckNonRepeatingShuttleLinkTime," + AnsiString(double(ForwardEventTime))
16331  + "," + AnsiString(double(ReverseEventTime)) + "," + AnsiString(RepeatMinutes) + "," + AnsiString(RepeatNumber));
16332  int ForwardSecs = int(double(ForwardEventTime) * 86400);
16333  int ReverseSecs = int(double(ReverseEventTime) * 86400);
16334  int RepeatSecs = RepeatMinutes * RepeatNumber * 60;
16335 
16336  if((ReverseSecs > (ForwardSecs + RepeatSecs + 10)) || (ReverseSecs < (ForwardSecs + RepeatSecs - 10)))
16337  {
16338  Utilities->CallLogPop(1369);
16339  return(false);
16340  }
16341  else
16342  {
16343  Utilities->CallLogPop(1370);
16344  return(true);
16345  }
16346 }
16347 
16348 // ---------------------------------------------------------------------------
16349 
16350 bool TTrainController::CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
16351 // check that each shuttle start ends either in Fns or Fxx-sh (though a single service can't end in Fxx-sh), and that
16352 // when the Fxx-sh is reached it references the original start and not another shuttle - not allowed to link two shuttles,
16353 // don't ever need to and as designed would skip repeats.
16354 
16355 // enter with TDEntry a shuttle start - Snt-sh or Sns-sh
16356 {
16357  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckShuttleServiceIntegrity," + AnsiString(TDEntryPtr->HeadCode));
16358  if(TDEntryPtr->ActionVector.back().FormatType != Repeat)
16359  {
16360  throw Exception("Error - last entry in " + TDEntryPtr->HeadCode + " service is not a repeat - should have already found this error");
16361  }
16362  TTrainDataEntry *ShuttleStartAddress = TDEntryPtr;
16363  AnsiString OriginalHeadCode = TDEntryPtr->HeadCode;
16364  AnsiString LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
16365 
16366  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
16367  {
16368  SecondPassMessage(GiveMessages, "Error in timetable - last event in shuttle service " + TDEntryPtr->HeadCode + " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
16369  TrainDataVector.clear();
16370  Utilities->CallLogPop(1091);
16371  return(false);
16372  }
16373  while(LastActionCommand == "Fns")
16374  {
16375  TDEntryPtr = (TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr;
16376  LastActionCommand = (TDEntryPtr->ActionVector.end() - 2)->Command;
16377  if((LastActionCommand != "Fns") && (LastActionCommand != "Fns-sh") && (LastActionCommand != "Frh-sh"))
16378  {
16379  SecondPassMessage(GiveMessages,
16380  "Error in timetable - last event in a continuation shuttle service (i.e links back to a shuttle) whose headcode is " + TDEntryPtr->HeadCode +
16381  " is not 'Fns', 'Fns-sh' or 'Frh-sh'");
16382  TrainDataVector.clear();
16383  Utilities->CallLogPop(1092);
16384  return(false);
16385  }
16386  }
16387  // exit the 'while' with LastActionCommand FSH-XX
16388  if((TDEntryPtr->ActionVector.end() - 2)->LinkedTrainEntryPtr != ShuttleStartAddress)
16389  {
16390  SecondPassMessage(GiveMessages, "Error in timetable - the event that ends service " + TDEntryPtr->HeadCode +
16391  " is a shuttle finish, but it doesn't link back to the start of the original shuttle starting service " + OriginalHeadCode +
16392  ". The linking of two or more shuttles is not permitted.");
16393  TrainDataVector.clear();
16394  Utilities->CallLogPop(1093);
16395  return(false);
16396  }
16397  Utilities->CallLogPop(1094);
16398  return(true);
16399 }
16400 
16401 // ---------------------------------------------------------------------------
16402 
16403 void TTrainController::TimetableMessage(bool GiveMessages, AnsiString Message)
16404 {
16405  if(!GiveMessages)
16406  {
16407  return;
16408  }
16409  // if(ServiceReference == "") ShowMessage(Message);
16410  if(!CheckHeadCodeValidity(12, false, ServiceReference))
16411  {
16412  ShowMessage(ServiceReference + " (not a valid service ref.): " + Message); //amended at v2.15.1 to give information on 'service' so can find it in lh list
16413  }
16414  // changed from above at v2.3.0 as a meaningless value for 'Timetable invalid - unable to find a valid start time on its own line' (uses last entry text)
16415  // false means don't give messages within the function
16416  else
16417  {
16418  ShowMessage("Service " + ServiceReference + ": " + Message);
16419  }
16420 }
16421 
16422 // ---------------------------------------------------------------------------
16423 
16424 void TTrainController::SecondPassMessage(bool GiveMessages, AnsiString Message)
16425 {
16426  if(!GiveMessages)
16427  {
16428  return;
16429  }
16430  ShowMessage(Message);
16431 }
16432 
16433 // ---------------------------------------------------------------------------
16434 
16435 AnsiString TTrainController::MinsToAnsiTime(int Input) //added at v2.15.0
16436 {
16437  TrainController->LogEvent("MinsToAnsiTime");
16438  Utilities->CallLog.push_back(Utilities->TimeStamp() + ",MinsToAnsiTime," + Input);
16439  int Mins = Input, Hrs = 0;
16440  while(Mins > 59)
16441  {
16442  Mins -= 60;
16443  Hrs++;
16444  }
16445  AnsiString AnsiMins = AnsiString(Mins);
16446  if(AnsiMins.Length() == 1)
16447  {
16448  AnsiMins = "0" + AnsiMins;
16449  }
16450  AnsiString AnsiHrs = AnsiString(Hrs);
16451  if(AnsiHrs.Length() == 1)
16452  {
16453  AnsiHrs = "0" + AnsiHrs;
16454  }
16455  Utilities->CallLogPop(2577);
16456  return(AnsiHrs + ':' + AnsiMins);
16457 }
16458 
16459 // $$$$$$$$$$$$$$$$$$$$$$$ End of Timetable Functions $$$$$$$$$$$$$$$$$$$$$$$
16460 // ---------------------------------------------------------------------------
16461 
16462 void TTrainController::LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
16463 // FailTrainEntry: 06:00:10 HELD: 2F43 can't enter railway, train obstructing entry position 57-N5
16464 // FailCreateTrain: 06:00:10 HELD: 2F43 can't be created, train obstructing start position 57-N5
16465 // FailCreateLockedRoute: 06:00:10 HELD: 2F43 can't be created on a locked route - start position 57-N5
16466 // FailEnterLockedRoute: 06:00:10 HELD: 2F43 can't enter on a locked route - start position 57-N5
16467 // FailCreatePoints: 06:00:10 HELD: 2F43 can't be created, points set against start position 57-N5
16468 // FailUnexpectedExitRailway: 06:00:10 ERROR: 2F43 left railway unexpectedly at position 57-N5
16469 // FailIncorrectExit: 06:00:10 ERROR: 2F43 left railway at an incorrect exit at position 57-N5
16470 // FailSPAD: 06:00:10 ERROR: 2F43 PASSED SIGNAL AT DANGER at position 57-N5
16471 // FailLockedRoute: 06:00:10 ERROR: SPAD Risk! Signals reset ahead of train, at position 57-N5
16472 // FailLocTooShort: 06:00:10 ERROR: 2F43 failed to split - location too short at Essex Road
16473 // FailSplitDueToOtherTrain: 06:00:10 HELD: 2F43 unable to split - another train is obstructing at Essex Road, please move it if possible
16474 // FailCrashed: 06:00:10: ERROR: 2F43 CRASHED INTO 3F43 at position 46-N7
16475 // FailDerailed: 06:00:10: ERROR: 2F43 DERAILED at position 46-N7
16476 // FailUnexpectedBuffers: 06:00:10: ERROR: 2F43 stopped at buffers unexpectedly at position 46-N7
16477 // FailMissedArrival: 06:00:10: ERROR: 2F43 failed to stop at Essex Road;
16478 // FailMissedSplit: 06:00:10: ERROR: 2F43 failed to split at Essex Road
16479 // FailMissedJBO: 06:00:10: ERROR: 2F43 failed to be joined by join other train at Essex Road
16480 // FailMissedDSC: 06:00:10: ERROR: 2F43 failed to change its description at Essex Road
16481 // FailMissedJoinOther: 06:00:10: ERROR: 2F43 failed to join other train at Essex Road
16482 // FailMissedTerminate: 06:00:10: ERROR: 2F43 failed to terminate at Essex Road
16483 // FailMissedNewService: 06:00:10: ERROR: 2F43 failed to form new service at Essex Road
16484 // FailMissedExitRailway: 06:00:10: ERROR: 2F43 failed to exit railway
16485 // FailMissedChangeDirection: 06:00:10: ERROR: 2F43 failed to change direction at Essex Road
16486 // FailMissedPass: 06:00:10: ERROR: 2F43 failed to pass Essex Road
16487 // FailBuffersPreventingStart: 06:00:10: ERROR: 2F43 facing buffers and unable to start at Essex Road
16488 // FailBufferCrash: 06:00:10: ERROR: 2F43 CRASHED INTO BUFFERS at 46-N7
16489 // FailLevelCrossingCrash: 06:00:10: ERROR: 2F43 CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at 46-N7
16490 // RouteForceCancelled: 06:00:10: ERROR: 2F43 forced a route cancellation by occupying it incorrectly at 46-N7
16491 // WaitingForJBO: 06:00:10: WARNING: 2F43 waiting to join 3F43 at Essex Road
16492 // WaitingForFJO: 06:00:10: WARNING: 2F43 waiting to be joined by 3F43 at Essex Road
16493 // FailEntryRouteSetAgainst: 06:00:10: WARNING: 2F43 can't enter railway, route set against it at entry position 57-N5 //added at v2.9.1
16494 // FailNoPowerUnableToDepart: 06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
16495 // FailTrainInFront 06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
16496 
16497 {
16498  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LogActionError," + HeadCode + "," + OtherHeadCode + "," +
16499  AnsiString(ActionEventType) + "," + LocationID);
16500  AnsiString BaseLog = "", Prefix = "", ErrorLog = "", WarningStr = "";
16501 
16502  TDateTime ActualTime = TrainController->TTClockTime; //moved from lower down at v2.9.1
16503  AnsiString TimeAndHeadCode = Utilities->Format96HHMMSS(ActualTime) + ": " + HeadCode; //added at v2.9.1 to give more info to user
16504 
16505  Prefix = " ERROR: ";
16506  if(ActionEventType == FailTrainEntry)
16507  {
16508  Prefix = " HELD: ";
16509  ErrorLog = " can't enter railway, train obstructing entry position ";
16510  WarningStr = " can't enter railway, train obstructing entry position ";
16511  Display->WarningLog(1, TimeAndHeadCode + WarningStr + LocationID);
16512  }
16513  else if(ActionEventType == FailEntryRouteSetAgainst) //added at v2.9.1
16514  {
16515  Prefix = " HELD: ";
16516  ErrorLog = " can't enter railway, route set against it at entry position ";
16517  WarningStr = " can't enter railway, route set against it at entry position ";
16518  Display->WarningLog(10, TimeAndHeadCode + WarningStr + LocationID);
16519  }
16520  else if(ActionEventType == FailCreateTrain)
16521  {
16522  Prefix = " HELD: ";
16523  ErrorLog = " can't be created, train obstructing ";
16524  WarningStr = " can't be created, train obstructing ";
16525  Display->WarningLog(2, TimeAndHeadCode + WarningStr + LocationID);
16526  }
16527  else if(ActionEventType == FailCreateLockedRoute)
16528  {
16529  Prefix = " HELD: ";
16530  ErrorLog = " can't be created on a locked route at ";
16531  WarningStr = " can't be created on a locked route at ";
16532  Display->WarningLog(4, TimeAndHeadCode + WarningStr + LocationID);
16533  }
16534  else if(ActionEventType == FailEnterLockedRoute)
16535  {
16536  Prefix = " HELD: ";
16537  ErrorLog = " can't enter on a locked route at ";
16538  WarningStr = " can't enter on a locked route at ";
16539  Display->WarningLog(5, TimeAndHeadCode + WarningStr + LocationID);
16540  }
16541  else if(ActionEventType == FailCreatePoints)
16542  {
16543  Prefix = " HELD: ";
16544  ErrorLog = " can't be created, points set wrongly at ";
16545  WarningStr = " can't be created, points set wrongly at ";
16546  Display->WarningLog(3, TimeAndHeadCode + WarningStr + LocationID);
16547  }
16548  else if(ActionEventType == FailUnexpectedExitRailway)
16549  {
16550  ErrorLog = " left railway unexpectedly at ";
16551  UnexpectedExits++;
16552  }
16553  else if(ActionEventType == FailIncorrectExit)
16554  {
16555  ErrorLog = " left railway at an incorrect exit at ";
16556  IncorrectExits++;
16557  }
16558  else if(ActionEventType == FailLocTooShort)
16559  {
16560  ErrorLog = " failed to split - location too short at ";
16561  WarningStr = " failed to split, location too short at ";
16562  Display->WarningLog(6, TimeAndHeadCode + WarningStr + LocationID);
16563  }
16564  else if(ActionEventType == FailSplitDueToOtherTrain)
16565  {
16566  Prefix = " HELD: ";
16567  ErrorLog = " unable to split - other train obstructing at ";
16568  WarningStr = " unable to split - other train obstructing at ";
16569  Display->WarningLog(7, TimeAndHeadCode + WarningStr + LocationID);
16570  }
16571  else if(ActionEventType == FailUnexpectedBuffers)
16572  {
16573  ErrorLog = " stopped at buffers unexpectedly at position ";
16574  }
16575  else if(ActionEventType == FailMissedArrival)
16576  {
16577  ErrorLog = " failed to stop at ";
16578  MissedStops++;
16579  }
16580  else if(ActionEventType == FailMissedSplit)
16581  {
16582  ErrorLog = " failed to split at ";
16584  }
16585  else if(ActionEventType == FailMissedJBO)
16586  {
16587  ErrorLog = " failed to be joined by other train at ";
16589  }
16590  else if(ActionEventType == FailMissedDSC) //new at v2.15.0
16591  {
16592  ErrorLog = " failed to change its description at ";
16593 // OtherMissedEvents++; shouldn't count
16594  }
16595  else if(ActionEventType == FailMissedJoinOther)
16596  {
16597  ErrorLog = " failed to join other train at ";
16599  }
16600  else if(ActionEventType == FailMissedTerminate)
16601  {
16602  ErrorLog = " failed to terminate at ";
16604  }
16605  else if(ActionEventType == FailMissedNewService)
16606  {
16607  ErrorLog = " failed to form new service at ";
16609  }
16610  else if(ActionEventType == FailMissedExitRailway)
16611  {
16612  ErrorLog = " failed to exit railway ";
16614  }
16615  else if(ActionEventType == FailMissedChangeDirection)
16616  {
16617  ErrorLog = " failed to change direction at ";
16618 // OtherMissedEvents++; //dropped at v2.12.0 as cdt shouldn't count
16619  }
16620  else if(ActionEventType == FailMissedPass)
16621  {
16622  ErrorLog = " failed to pass ";
16623 // OtherMissedEvents++; //dropped at v2.12.0 as missed pass shouldn't count
16624  }
16625  else if(ActionEventType == FailBuffersPreventingStart)
16626  {
16627  ErrorLog = " facing buffers and unable to start at ";
16628  }
16629  else if(ActionEventType == FailDerailed)
16630  {
16631  ErrorLog = " DERAILED at position ";
16632  Prefix = " DERAILMENT: ";
16633  Derailments++;
16634  }
16635  else if(ActionEventType == FailBufferCrash)
16636  {
16637  ErrorLog = " CRASHED INTO BUFFERS at ";
16638  Prefix = " CRASH: ";
16639  CrashedTrains++;
16640  }
16641  else if(ActionEventType == FailLevelCrossingCrash)
16642  {
16643  ErrorLog = " CRASHED INTO ROAD TRAFFIC AT A LEVEL CROSSING at ";
16644  Prefix = " CRASH: ";
16645  CrashedTrains++;
16646  }
16647  else if(ActionEventType == FailCrashed)
16648  {
16649  ErrorLog = " CRASHED INTO " + OtherHeadCode + " at position ";
16650  Prefix = " CRASH: ";
16651  CrashedTrains++;
16652  CrashedTrains++;
16653  }
16654  else if(ActionEventType == FailSPAD)
16655  {
16656  ErrorLog = " PASSED SIGNAL AT DANGER at position ";
16657  Prefix = " SPAD: ";
16658  SPADEvents++;
16659  }
16660  else if(ActionEventType == FailLockedRoute)
16661  {
16662  ErrorLog = "Signals reset ahead of train, route cancelled at position ";
16663  Prefix = " SPAD RISK: ";
16664  SPADRisks++;
16665  }
16666  else if(ActionEventType == RouteForceCancelled)
16667  {
16668  ErrorLog = " forced a route cancellation by occupying it incorrectly at ";
16669  }
16670  else if(ActionEventType == WaitingForJBO)
16671  {
16672  Prefix = " WARNING: ";
16673  ErrorLog = " waiting to join " + OtherHeadCode + " at ";
16674  WarningStr = " waiting to join " + OtherHeadCode + " at ";
16675  Display->WarningLog(8, TimeAndHeadCode + WarningStr + LocationID);
16676  }
16677  else if(ActionEventType == WaitingForFJO)
16678  {
16679  Prefix = " WARNING: ";
16680  ErrorLog = " waiting to be joined by " + OtherHeadCode + " at ";
16681  WarningStr = " waiting to be joined by " + OtherHeadCode + " at ";
16682  Display->WarningLog(9, TimeAndHeadCode + WarningStr + LocationID);
16683  }
16684  else if(ActionEventType == FailNoPowerUnableToDepart) //06:00:10: WARNING: 2F43 is without power so it can't depart from Essex Road // added at v2.19.1
16685  {
16686  Prefix = " WARNING: ";
16687  ErrorLog = " is without power so it can't depart from ";
16688  WarningStr = " is without power so it can't depart from ";
16689  Display->WarningLog(27, TimeAndHeadCode + WarningStr + LocationID);
16690  }
16691  else if(ActionEventType == FailTrainInFront) //06:00:10: WARNING: 2F43 can't depart because there is a train in front at Essex Road // added at v2.19.1
16692  {
16693  Prefix = " WARNING: ";
16694  ErrorLog = " can't depart because there is a train in front at ";
16695  WarningStr = " can't depart because there is a train in front at ";
16696  Display->WarningLog(28, TimeAndHeadCode + WarningStr + LocationID);
16697  }
16698 
16699  BaseLog = Utilities->Format96HHMMSS(ActualTime) + Prefix + HeadCode;
16700  PerfLogForm->PerformanceLog(4, BaseLog + ErrorLog + LocationID);
16701  Utilities->CallLogPop(1371);
16702 }
16703 
16704 // ---------------------------------------------------------------------------
16705 
16707 {
16708 /* //for testing purposes
16709  TrainDataEntry
16710  AnsiString HeadCode, Description;//null on creation
16711  int StartSpeed, MaxRunningSpeed;//both kph
16712  int RepeatNumber;
16713  TActionVector ActionVector;
16714  TTrainOperatingDataVector TrainOperatingDataVector;//no of repeats + 1
16715  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; RepeatNumber=0;}
16716 
16717  ActionVectorEntry
16718  TTimetableEntryType FormatType;
16719  TDateTime EventTime, ArrivalTime, DepartureTime;//zeroed on creation so change to -1 as a marker for 'not set'
16720  AnsiString LocationName, Command, OtherHeadCode;//null on creation
16721  TActionVectorEntry *OtherHeadCodeStartingEntryPtr;
16722  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
16723  int RepeatNumber;
16724 
16725  TrainOperatingData
16726  int Mass, MaxBrakeRate, PowerAtRail;//kg;m/s/s;W
16727  int TrainID;
16728  TRunningEntry RunningEntry;
16729  TDateTime StartTime;
16730  AnsiString HeadCode;
16731 */
16732  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveTrainDataVectorToFile");
16733  std::ofstream OutFile("TrainData.csv");
16734 
16735  if(OutFile == 0)
16736  {
16737  ShowMessage("Output file TrainData.csv failed to open");
16738  Utilities->CallLogPop(1372);
16739  return;
16740  }
16741  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
16742  {
16743  const TTrainDataEntry &TDEntry = TrainDataVector.at(x);
16744  OutFile << "HeadCode" << ',' << "Description" << ',' << "StartSpeed" << ',' << "MaxRunningSpeed" << ',' << "NumberOfTrains" << '\n' << '\n';
16745 
16746  OutFile << TDEntry.HeadCode.c_str() << ',' << TDEntry.FixedDescription.c_str() /* name changed at v2.16.1*/
16747  << ',' << TDEntry.StartSpeed << ',' << TDEntry.MaxRunningSpeed << ',' << TDEntry.NumberOfTrains << '\n' << '\n';
16748 
16749  OutFile << ',' << "FormatType" << ',' << "EventTime" << ',' << "ArrivalTime" << ',' << "DepartureTime" << ',' << "LocationName" << ',' << "Command" <<
16750  ',' << "OtherHeadCode" << ',' << "LinkedTrainEntryPtr" << ',' << "RearStartOrRepeatMins" << ',' << "FrontStartOrRepeatDigits" << ',' <<
16751  "RepeatNumber" << '\n' << '\n';
16752  for(unsigned int y = 0; y < TrainDataVector.at(x).ActionVector.size(); y++)
16753  {
16754  TActionVectorEntry &AVEntry = TrainDataVector.at(x).ActionVector.at(y);
16755  AnsiString TimetableEntryTypeStr;
16756  // NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere, FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat
16757  switch(AVEntry.FormatType)
16758  {
16759  case 0:
16760  {
16761  TimetableEntryTypeStr = "NoFormat";
16762  break;
16763  }
16764 
16765  case 1:
16766  {
16767  TimetableEntryTypeStr = "TimeLoc";
16768  break;
16769  }
16770 
16771  case 2:
16772  {
16773  TimetableEntryTypeStr = "TimeTimeLoc";
16774  break;
16775  }
16776 
16777  case 3:
16778  {
16779  TimetableEntryTypeStr = "TimeCmd";
16780  break;
16781  }
16782 
16783  case 4:
16784  {
16785  TimetableEntryTypeStr = "StartNew";
16786  break;
16787  }
16788 
16789  case 5:
16790  {
16791  TimetableEntryTypeStr = "TimeCmdHeadCode";
16792  break;
16793  }
16794 
16795  case 6:
16796  {
16797  TimetableEntryTypeStr = "FinRemHere";
16798  break;
16799  }
16800 
16801  case 7:
16802  {
16803  TimetableEntryTypeStr = "FNSShuttle";
16804  break;
16805  }
16806 
16807  case 8:
16808  {
16809  TimetableEntryTypeStr = "SNTShuttle";
16810  break;
16811  }
16812 
16813  case 9:
16814  {
16815  TimetableEntryTypeStr = "SNSShuttle";
16816  break;
16817  }
16818 
16819  case 10:
16820  {
16821  TimetableEntryTypeStr = "SNSNonRepeatFromShuttle";
16822  break;
16823  }
16824 
16825  case 11:
16826  {
16827  TimetableEntryTypeStr = "FSHNewService";
16828  break;
16829  }
16830 
16831  case 12:
16832  {
16833  TimetableEntryTypeStr = "Repeat";
16834  break;
16835  }
16836 
16837  default:
16838  {
16839  TimetableEntryTypeStr = "Default";
16840  break;
16841  }
16842  }
16843  OutFile << ',' << TimetableEntryTypeStr.c_str() << ',' << Utilities->Format96HHMM(AVEntry.EventTime).c_str() << ',' << Utilities->Format96HHMM
16844  (AVEntry.ArrivalTime).c_str() << ',' << Utilities->Format96HHMM(AVEntry.DepartureTime).c_str() << ',' << AVEntry.LocationName.c_str()
16845  << ',' << AVEntry.Command.c_str() << ',' << AVEntry.OtherHeadCode.c_str()
16846  << ',' << AVEntry.LinkedTrainEntryPtr << ',' << AVEntry.RearStartOrRepeatMins << ',' << AVEntry.FrontStartOrRepeatDigits << ',' <<
16847  AVEntry.NumberOfRepeats << '\n';
16848  }
16849  OutFile << '\n';
16850  OutFile << ',' << ',' << "Mass" << ',' << "MaxBrakeRate" << ',' << "PowerAtRail" << ',' << "TrainID" << ',' << "RunningEntry" << '\n' << '\n';
16851  for(unsigned int y = 0; y < TrainDataVector.at(x).TrainOperatingDataVector.size(); y++)
16852  {
16853  TTrainOperatingData TOD = TrainDataVector.at(x).TrainOperatingDataVector.at(y);
16854  AnsiString RunningEntryStr;
16855  // NotStarted, Running, Exited
16856  switch(TOD.RunningEntry)
16857  {
16858  case 0:
16859  {
16860  RunningEntryStr = "NotStarted";
16861  break;
16862  }
16863 
16864  case 1:
16865  {
16866  RunningEntryStr = "Running";
16867  break;
16868  }
16869 
16870  case 2:
16871  {
16872  RunningEntryStr = "Exited";
16873  break;
16874  }
16875  }
16876  OutFile << ',' << ',' << TOD.TrainID << ',' << RunningEntryStr.c_str() << ',' << '\n';
16877  }
16878  OutFile << '\n';
16879  }
16880  OutFile.close();
16881  Utilities->CallLogPop(1373);
16882 }
16883 
16884 // ---------------------------------------------------------------------------
16885 
16886 void TTrainController::StopTTClockMessage(int Caller, AnsiString Message)
16887 // ShowMessage stops everything so this function used where a message is needed when may be in Operating mode.
16888 // The timetable Restart and BaseTimes are reset so the timetable clock stops & restarts when 'OK' button pressed (in ClockTimer2 when StopTTClockFlag is false)
16889 {
16890  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",StopTTClockMessage," + Message);
16891  StopTTClockFlag = true; // so TTClock stopped during MasterClockTimer function
16893  ShowMessage(Message);
16894  BaseTime = TDateTime::CurrentDateTime();
16895  StopTTClockFlag = false;
16896  Utilities->CallLogPop(1374);
16897 }
16898 
16899 // ---------------------------------------------------------------------------
16900 
16901 void TTrainController::SaveSessionTrains(int Caller, std::ofstream &SessionFile)
16902 // save *TrainDataEntryPtr & *ActionVectorEntryPtr as integer offsets
16903 // from the start of the relevant vectors. Can't save the pointer values
16904 // as these will be different each time the vectors are created
16905 {
16906  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionTrains");
16907  Utilities->SaveFileInt(SessionFile, TrainVector.size());
16908  for(unsigned int x = 0; x < TrainVector.size(); x++)
16909  {
16910  TrainVectorAt(55, x).SaveOneSessionTrain(0, SessionFile);
16911  }
16912  Utilities->CallLogPop(1375);
16913 }
16914 
16915 // ---------------------------------------------------------------------------
16916 
16917 void TTrainController::LoadSessionTrains(int Caller, std::ifstream &SessionFile)
16918 {
16919  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionTrains");
16920  int NumberOfTrains = Utilities->LoadFileInt(SessionFile);
16921  TTrain *NewTrain = new TTrain(1, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
16922  // by zero error in calculating AValue, use 1
16923  for(int x = 0; x < NumberOfTrains; x++)
16924  {
16925  *NewTrain = TTrain(2, 0, 0, "", 0, 1, 0, 0, 0, (TTrainMode)0, 0, 0, 0, 0, 0); // have to have >0 for mass, else have divide
16926  // by zero error in calculating AValue, use 1
16927  NewTrain->LoadOneSessionTrain(0, SessionFile);
16928  if((NewTrain->EntrySpeed < 1) && (NewTrain->PowerAtRail < 1))
16929  // added at v2.4.0. have to include as that value not stored in session file
16930  {
16931  NewTrain->StoppedWithoutPower = true;
16932  }
16933  TrainVector.push_back(*NewTrain);
16934  LastTrainLoaded = x;
16935  }
16936  delete NewTrain;
16937  Utilities->CallLogPop(1376);
16938 }
16939 
16940 // ---------------------------------------------------------------------------
16941 
16942 bool TTrainController::CheckSessionTrains(int Caller, std::ifstream &InFile)
16943 {
16944  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionTrains");
16945  int NumberOfTrains;
16946 
16947  if(!Utilities->CheckAndReadFileInt(InFile, 0, 10000, NumberOfTrains))
16948  {
16949  Utilities->CallLogPop(1377);
16950  return(false);
16951  }
16952  for(int x = 0; x < NumberOfTrains; x++)
16953  {
16954  if(!(TTrain::CheckOneSessionTrain(InFile)))
16955  {
16956  Utilities->CallLogPop(1378);
16957  return(false);
16958  }
16959  }
16960  Utilities->CallLogPop(1379);
16961  return(true);
16962 }
16963 
16964 // ---------------------------------------------------------------------------
16965 
16966 void TTrainController::SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
16967 {
16968  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionLockedRoutes");
16969  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.size());
16970  for(unsigned int x = 0; x < AllRoutes->LockedRouteVector.size(); x++)
16971  {
16972  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RouteNumber);
16973  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).RearTrackVectorPosition);
16974  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastTrackVectorPosition);
16975  Utilities->SaveFileInt(SessionFile, AllRoutes->LockedRouteVector.at(x).LastXLinkPos);
16976  Utilities->SaveFileDouble(SessionFile, double(AllRoutes->LockedRouteVector.at(x).LockStartTime));
16977  }
16978  Utilities->CallLogPop(1380);
16979 }
16980 
16981 // ---------------------------------------------------------------------------
16982 
16983 void TTrainController::LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
16984 {
16985  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionLockedRoutes");
16986  TAllRoutes::TLockedRouteClass LockedRouteObject;
16987  int LockedRouteVectorSize = Utilities->LoadFileInt(SessionFile);
16988 
16989  for(int x = 0; x < LockedRouteVectorSize; x++)
16990  {
16991  LockedRouteObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
16992  LockedRouteObject.RearTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
16993  LockedRouteObject.LastTrackVectorPosition = Utilities->LoadFileInt(SessionFile);
16994  LockedRouteObject.LastXLinkPos = Utilities->LoadFileInt(SessionFile);
16995  double LockStartTimeDouble = Utilities->LoadFileDouble(SessionFile);
16996  LockedRouteObject.LockStartTime = TDateTime(LockStartTimeDouble);
16997  AllRoutes->LockedRouteVector.push_back(LockedRouteObject);
16998  }
16999  Utilities->CallLogPop(1381);
17000 }
17001 
17002 // ---------------------------------------------------------------------------
17003 
17004 bool TTrainController::CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
17005 {
17006  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionLockedRoutes");
17007  int LockedRouteVectorSize;
17008 
17009  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, LockedRouteVectorSize))
17010  {
17011  Utilities->CallLogPop(1382);
17012  return(false);
17013  }
17014  for(int x = 0; x < LockedRouteVectorSize; x++)
17015  {
17016  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17017  {
17018  Utilities->CallLogPop(1383);
17019  return(false);
17020  }
17021  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17022  {
17023  Utilities->CallLogPop(1384);
17024  return(false);
17025  }
17026  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17027  {
17028  Utilities->CallLogPop(1385);
17029  return(false);
17030  }
17031  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17032  {
17033  Utilities->CallLogPop(1386);
17034  return(false);
17035  }
17036  if(!Utilities->CheckFileDouble(SessionFile))
17037  {
17038  Utilities->CallLogPop(1387);
17039  return(false);
17040  }
17041  }
17042  Utilities->CallLogPop(1388);
17043  return(true);
17044 }
17045 
17046 // ---------------------------------------------------------------------------
17047 
17048 void TTrainController::SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
17049 {
17050  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SaveSessionContinuationAutoSigEntries");
17051  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.size());
17052  for(unsigned int x = 0; x < ContinuationAutoSigVector.size(); x++)
17053  {
17054  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).RouteNumber);
17055  Utilities->SaveFileInt(SessionFile, ContinuationAutoSigVector.at(x).AccessNumber);
17056  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).FirstDelay);
17057  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).SecondDelay);
17058  Utilities->SaveFileDouble(SessionFile, ContinuationAutoSigVector.at(x).ThirdDelay);
17059  Utilities->SaveFileDouble(SessionFile, double(ContinuationAutoSigVector.at(x).PassoutTime));
17060  }
17061  Utilities->CallLogPop(1389);
17062 }
17063 
17064 // ---------------------------------------------------------------------------
17065 
17066 void TTrainController::LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17067 {
17068  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",LoadSessionContinuationAutoSigEntries");
17069  TContinuationAutoSigEntry ContinuationAutoSigObject;
17070  int ContinuationAutoSigVectorSize = Utilities->LoadFileInt(SessionFile);
17071 
17072  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17073  {
17074  ContinuationAutoSigObject.RouteNumber = Utilities->LoadFileInt(SessionFile);
17075  ContinuationAutoSigObject.AccessNumber = Utilities->LoadFileInt(SessionFile);
17076  ContinuationAutoSigObject.FirstDelay = Utilities->LoadFileDouble(SessionFile);
17077  ContinuationAutoSigObject.SecondDelay = Utilities->LoadFileDouble(SessionFile);
17078  ContinuationAutoSigObject.ThirdDelay = Utilities->LoadFileDouble(SessionFile);
17079  double PassoutTimeDouble = Utilities->LoadFileDouble(SessionFile);
17080  ContinuationAutoSigObject.PassoutTime = TDateTime(PassoutTimeDouble);
17081  ContinuationAutoSigVector.push_back(ContinuationAutoSigObject);
17082  }
17083  Utilities->CallLogPop(1390);
17084 }
17085 
17086 // ---------------------------------------------------------------------------
17087 
17088 bool TTrainController::CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
17089 {
17090  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CheckSessionContinuationAutoSigEntries");
17091  int ContinuationAutoSigVectorSize;
17092 
17093  if(!Utilities->CheckAndReadFileInt(SessionFile, 0, 10000, ContinuationAutoSigVectorSize))
17094  {
17095  Utilities->CallLogPop(1391);
17096  return(false);
17097  }
17098  for(int x = 0; x < ContinuationAutoSigVectorSize; x++)
17099  {
17100  if(!Utilities->CheckFileInt(SessionFile, 0, 1000000))
17101  {
17102  Utilities->CallLogPop(1392);
17103  return(false);
17104  }
17105  if(!Utilities->CheckFileInt(SessionFile, 0, 3))
17106  {
17107  Utilities->CallLogPop(1393);
17108  return(false);
17109  }
17110  if(!Utilities->CheckFileDouble(SessionFile))
17111  {
17112  Utilities->CallLogPop(1405);
17113  return(false);
17114  }
17115  if(!Utilities->CheckFileDouble(SessionFile))
17116  {
17117  Utilities->CallLogPop(1406);
17118  return(false);
17119  }
17120  if(!Utilities->CheckFileDouble(SessionFile))
17121  {
17122  Utilities->CallLogPop(1407);
17123  return(false);
17124  }
17125  if(!Utilities->CheckFileDouble(SessionFile))
17126  {
17127  Utilities->CallLogPop(1394);
17128  return(false);
17129  }
17130  }
17131  Utilities->CallLogPop(1395);
17132  return(true);
17133 }
17134 
17135 // ---------------------------------------------------------------------------
17136 
17137 /*
17138  class TContinuationTrainExpectationEntry //for expected trains at continuation entries
17139  {
17140  public:
17141  AnsiString Description; ///< service description
17142  AnsiString HeadCode; ///< service headcode
17143  int RepeatNumber; ///< service RepeatNumber
17144  int IncrementalMinutes; ///< Repeat separation in minutes
17145  int IncrementalDigits; ///< Repeat headcode separation
17146  int VectorPosition; ///< TrackVectorPosition for the continuation element
17147  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
17148  };
17149 
17150 
17151  typedef std::multimap<TDateTime,TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
17152  typedef pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair;
17153 */
17154 
17156 // build this into timetable load so session loading can use it too
17157 // being a multimap it automatically sorts in ascending EventTime order
17158 {
17159  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",BuildContinuationTrainExpectationMultiMap");
17161  // need to clear as this called twice when load a session
17162  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17163  {
17164  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
17165  const TActionVectorEntry &AVFirstEntry = TDEntry.ActionVector.at(0);
17166  const TActionVectorEntry &AVLastEntry = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
17167 
17168  if(AVFirstEntry.Command == "Snt")
17169  // new train (no need to include Snt-sh since they can't start at a continuation)
17170  {
17173  {
17175  CTEEntry.VectorPosition = AVFirstEntry.RearStartOrRepeatMins;
17176  // retains this value for all repeats
17177  CTEEntry.RepeatNumber = 0; // for first entry
17178  CTEEntry.TrainDataEntryPtr = &TDEntry;
17179  // retains this value for all repeats
17180  CTEEntry.HeadCode = TDEntry.HeadCode;
17181  CTEEntry.FixedDescription = TDEntry.FixedDescription; //name changed at v2.16.1
17182  CTEEntry.IncrementalMinutes = 0;
17183  CTEEntry.IncrementalDigits = 0;
17184  if(AVLastEntry.FormatType == Repeat)
17185  {
17186  CTEEntry.IncrementalMinutes = AVLastEntry.RearStartOrRepeatMins;
17187  // retains this value or 0 for all repeats
17188  CTEEntry.IncrementalDigits = AVLastEntry.FrontStartOrRepeatDigits;
17189  // retains this value or 0 for all repeats
17190  }
17191  CTEMMP.first = AVFirstEntry.EventTime;
17192  CTEMMP.second = CTEEntry;
17193  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
17194  // base entry
17195  if(TDEntry.NumberOfTrains > 1)
17196  {
17197  if(AVLastEntry.FormatType != Repeat)
17198  {
17199  throw Exception("Error, Last ActionVectorEntry not a repeat in BuildContinuationTrainExpectationMultiMap");
17200  }
17201  for(int y = 1; y < TDEntry.NumberOfTrains; y++)
17202  {
17203  CTEEntry.RepeatNumber = y;
17204  CTEEntry.HeadCode = GetRepeatHeadCode(23, TDEntry.HeadCode, y, AVLastEntry.FrontStartOrRepeatDigits);
17205  // CTEEntry.VectorPosition stays same
17206  CTEMMP.first = GetRepeatTime(3, AVFirstEntry.EventTime, y, AVLastEntry.RearStartOrRepeatMins);
17207  CTEMMP.second = CTEEntry;
17208  ContinuationTrainExpectationMultiMap.insert(CTEMMP);
17209  }
17210  }
17211  }
17212  }
17213  }
17214  Utilities->CallLogPop(1396);
17215 }
17216 
17217 // ---------------------------------------------------------------------------
17218 
17220 {
17221  // called when WarningFlashCount == 0 or when press zoomout button
17222  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",PlotTrainsInZoomOutMode");
17223  if(!Display->ZoomOutFlag)
17224  {
17225  Utilities->CallLogPop(1156);
17226  return;
17227  }
17228  for(unsigned int x = 0; x < TrainVector.size(); x++)
17229  {
17230  // plot blanks & track for all train, even if to be overplotted, since when flashing need to overplot all anyway
17231  // if OldPlotElement[x] == -1 then ignore (not plotted)
17233  TrainVectorAt(57, x).PlotTrainInZoomOutMode(0, Flash);
17234  }
17235  Display->Update();
17236  // need to keep this since Update() not called for PlotSmallOutput as too slow
17237  Utilities->CallLogPop(742);
17238 }
17239 
17240 // ---------------------------------------------------------------------------
17241 
17242 TTrain &TTrainController::TrainVectorAt(int Caller, int VecPos)
17243 {
17244  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",TrainVectorAt," + AnsiString(VecPos));
17245  if((VecPos < 0) || (VecPos >= (int)TrainVector.size()))
17246  {
17247  throw Exception("Out of Range Error, vector size: " + AnsiString(TrainVector.size()) + ", VecPos: " + AnsiString(VecPos) + " in TrainVectorAt");
17248  }
17249  Utilities->CallLogPop(740);
17250  return(TrainVector.at(VecPos));
17251 }
17252 
17253 // ---------------------------------------------------------------------------
17254 
17255 void TTrainController::CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
17256 {
17257  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateFormattedTimetable");
17258  AnsiString RetStr = "", PartStr = "";
17259 
17260 
17261 /*
17262  Have description & mass etc for train at top - header, then array of actions
17263 
17264  class TActionVectorEntry
17265  {
17266  public:
17267  AnsiString LocationName, Command, OtherHeadCode, NonRepeatingShuttleLinkHeadCode;
17269  bool SignallerControl;
17271  bool Warning;
17273  int NumberOfRepeats;
17275  int RearStartOrRepeatMins, FrontStartOrRepeatDigits;
17277  TDateTime EventTime, ArrivalTime, DepartureTime;
17279  TNumList ExitList;
17281  TTimetableFormatType FormatType;
17283  TTimetableLocationType LocationType;
17285  TTimetableSequenceType SequenceType;
17287  TTimetableShuttleLinkType ShuttleLinkType;
17289  TTrainDataEntry *LinkedTrainEntryPtr;
17291  TTrainDataEntry *NonRepeatingShuttleLinkEntryPtr;
17293 
17294  typedef std::vector<TActionVectorEntry> TActionVector;//contains all actions for a single train
17295 
17296  enum TRunningEntry {NotStarted, Running, Exited};//contains status info for each train
17297 
17298  class TTrainOperatingData
17299  {
17300  public:
17301  int TrainID;
17302  TActionEventType EventReported;
17303  TRunningEntry RunningEntry;
17304 
17305  //inline function
17306  TTrainOperatingData() {TrainID = -1; EventReported= NoEvent; RunningEntry=NotStarted;}//ID -1 = marker for not running
17307  };
17308 
17309  typedef std::vector<TTrainOperatingData> TTrainOperatingDataVector;
17310 
17311  class TTrainDataEntry
17312  {
17313  public:
17314  AnsiString HeadCode, ServiceReference, Description;
17316  double MaxBrakeRate;
17318  double MaxRunningSpeed;
17320  double PowerAtRail;
17322  int Mass;
17324  int NumberOfTrains;
17326  int SignallerSpeed;
17328  int StartSpeed;
17330  TActionVector ActionVector;
17332  TTrainOperatingDataVector TrainOperatingDataVector;
17334 
17335  //inline function
17336  TTrainDataEntry() {StartSpeed=0; MaxRunningSpeed=0; NumberOfTrains=0;}
17337  };
17338 
17339  typedef std::vector<TTrainDataEntry> TTrainDataVector;//object is a member of TTrainController & contains the whole timetable
17340 
17341  //formatted timetable types
17342  class TOneTrainFormattedEntry
17343  {
17344  AnsiString Action;//includes location if relevanr
17345  AnsiString Time;
17346  };
17347 
17348  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
17349 
17350  class TOneCompleteFormattedTrain//headcode + list of actions
17351  {
17352  public:
17353  AnsiString HeadCode;
17354  TOneFormattedTrainVector OneFormattedTrainVector;
17355  };
17356 
17357  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
17358 
17359  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
17360  {
17361  public:
17362  AnsiString Header;//description, mass, power, brake rate etc
17363  int NumberOfTrains;// number of repeats + 1
17364  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
17365  };
17366 
17367 
17368  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
17369  //end of formatted timetable types
17370 
17371 */
17372 
17373  AnsiString TTFileName = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
17374 
17375  // format "16/06/2009 20:55:17"
17376  // avoid characters in filename:= / \ : * ? " < > |
17377  TTFileName = CurDir + "\\Formatted timetables\\Timetable " + TTFileName + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
17378 
17379  AnsiString ShortTTName = "";
17380 
17381  for(int x = TTFileName.Length(); x > 0; x--)
17382  {
17383  if(TTFileName[x] == '\\')
17384  {
17385  ShortTTName = TTFileName.SubString(x + 1, TTFileName.Length() - x - 4);
17386  break;
17387  }
17388  }
17389 
17390  ShowMessage("Creates two timetables named " + ShortTTName +
17391  " in the 'Formatted timetables' folder, one in service order in '.csv' format, and one in chronological order in '.txt' format");
17392 
17393  Screen->Cursor = TCursor(-11); // Hourglass
17394 
17395  AnsiString FormatNoDPStr = "#######0";
17396  AnsiString TableTitle = "", TimetableTimeStr = "", MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "", FirstHeadCode = "", Header = "";
17397 
17399  TableTitle = "Railway: " + RailwayTitle + "; Timetable: " + TimetableTitle + "; Start time: " + TimetableTimeStr;
17400  TAllFormattedTrains *AllTTTrains = new TAllFormattedTrains;
17401 
17402  // all timetable in formatted form
17403  //create the AllTTTrains vector
17404  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
17405  {
17406  MassStr = "", PowerStr = "", BrakeStr = "", MaxSpeedStr = "";
17407  const TTrainDataEntry &TrainDataEntry = TrainDataVector.at(x);
17408  if(TrainDataEntry.Mass > 0)
17409  {
17410  MassStr = "; Mass " + AnsiString::FormatFloat(FormatNoDPStr, ((double)TrainDataEntry.Mass) / 1000) + "Te; ";
17411  }
17412  if(TrainDataEntry.PowerAtRail > 0)
17413  {
17414  PowerStr = "Power " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.PowerAtRail / 1000 / 0.8) + "kW; ";
17415  }
17416  if(TrainDataEntry.MaxBrakeRate > 0)
17417  {
17418  BrakeStr = "Brake force " + AnsiString::FormatFloat(FormatNoDPStr, (TrainDataEntry.MaxBrakeRate * TrainDataEntry.Mass / 9810)) + "Te; ";
17419  }
17420  if(TrainDataEntry.MaxRunningSpeed > 0)
17421  {
17422  MaxSpeedStr = "Maximum speed " + AnsiString::FormatFloat(FormatNoDPStr, TrainDataEntry.MaxRunningSpeed) + " km/h";
17423  }
17424  FirstHeadCode = TrainDataEntry.HeadCode;
17425  int IncDigits = 0, IncMinutes = 0;
17426  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
17427  if(!ActionVector.empty())
17428  {
17429  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
17430  {
17431  IncDigits = ActionVector.at(ActionVector.size() - 1).FrontStartOrRepeatDigits;
17432  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
17433  }
17434  }
17435  TTrainFormattedInformation OneTTLine;
17436  // contains all information for a single TT entry (including repeats)
17437  for(int y = 0; y < TrainDataEntry.NumberOfTrains; y++)
17438  {
17439  OneTTLine.Header = "";
17440  if((TrainDataEntry.FixedDescription != "") && (MassStr != "")) //name changed at v2.16.1
17441  {
17442  OneTTLine.Header = TrainDataEntry.FixedDescription + MassStr + PowerStr + BrakeStr + MaxSpeedStr; //name changed at v2.16.1
17443  }
17444  else if(TrainDataEntry.FixedDescription != "") //name changed at v2.16.1
17445  {
17446  OneTTLine.Header = TrainDataEntry.FixedDescription; //name changed at v2.16.1
17447  }
17448  OneTTLine.NumberOfTrains = TrainDataEntry.NumberOfTrains;
17449  TOneCompleteFormattedTrain OneTTTrain; // headcode + list of actions
17450  for(unsigned int z = 0; z < ActionVector.size(); z++)
17451  {
17452  TOneTrainFormattedEntry OneTTEntry;
17453  OneTTTrain.HeadCode = GetRepeatHeadCode(24, FirstHeadCode, y, IncDigits);
17454  TActionVectorEntry ActionVectorEntry = ActionVector.at(z);
17455  AnsiString PartStr = "", TimeStr = "";
17456 /*
17457  enum TTimetableFormatType {NoFormat, TimeLoc, TimeTimeLoc, TimeCmd, StartNew, TimeCmdHeadCode, FinRemHere,
17458  FNSNonRepeatToShuttle, SNTShuttle, SNSShuttle, SNSNonRepeatFromShuttle, FSHNewService, Repeat, PassTime,
17459  ExitRailway};
17460  enum TTimetableSequenceType {NoSequence, StartSequence, FinishSequence, IntermediateSequence, SequTypeForRepeatEntry};
17461  enum TTimetableLocationType {NoLocation, AtLocation, EnRoute, LocTypeForRepeatEntry};
17462  enum TTimetableShuttleLinkType {NoShuttleLink, NotAShuttleLink, ShuttleLink, ShuttleLinkTypeForRepeatEntry};
17463 */
17464  if(ActionVectorEntry.SequenceType == StartSequence)
17465  {
17466  if(ActionVectorEntry.FormatType == StartNew)
17467  {
17468  if(ActionVectorEntry.LocationName != "")
17469  {
17470  if(Track->TrackElementAt(742, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
17471  {
17472  PartStr = "Enters at " + ActionVectorEntry.LocationName;
17473  }
17474  else
17475  {
17476  PartStr = "Created at " + ActionVectorEntry.LocationName;
17477  }
17478  }
17479  else // may be a named continuation or other element, and if so report that
17480  {
17481  AnsiString LocName = Track->TrackElementAt(739, ActionVectorEntry.RearStartOrRepeatMins).ActiveTrackElementName;
17482  if(Track->TrackElementAt(740, ActionVectorEntry.RearStartOrRepeatMins).TrackType == Continuation)
17483  {
17484  if(LocName != "")
17485  {
17486  PartStr = "Enters at " + LocName;
17487  }
17488  else // use rear position if it's a continuation
17489  {
17490  PartStr = "Enters at " + Track->TrackElementAt(737, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
17491  }
17492  }
17493  else // not a continuation
17494  {
17495  if(LocName != "")
17496  // if not a continuation then LocName should be same as ActionVectorEntry.LocationName
17497  // but include anyway
17498  {
17499  PartStr = "Created at " + LocName;
17500  }
17501  else // use rear position again
17502  {
17503  PartStr = "Created at " + Track->TrackElementAt(741, ActionVectorEntry.RearStartOrRepeatMins).ElementID;
17504  }
17505  }
17506  }
17507  TimeStr = Utilities->Format96HHMM(GetRepeatTime(20, ActionVectorEntry.EventTime, y, IncMinutes));
17508  }
17509  else if(ActionVectorEntry.FormatType == SNTShuttle)
17510  {
17511  if(y == 0) // first train
17512  {
17513  PartStr = "Enters at " + ActionVectorEntry.LocationName;
17514  TimeStr = Utilities->Format96HHMM(GetRepeatTime(21, ActionVectorEntry.EventTime, y, IncMinutes));
17515  }
17516  else
17517  {
17518  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
17519  TimeStr = GetRepeatHeadCode(45, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
17520  Utilities->Format96HHMM(GetRepeatTime(26, ActionVectorEntry.EventTime, y, IncMinutes));
17521  } // y-1 for headcode above since it is the last repeat value that the train is from
17522 
17523  }
17524  else if(ActionVectorEntry.Command == "Sfs")
17525  {
17526  PartStr = "New service at " + ActionVectorEntry.LocationName + " split from";
17527  TimeStr = GetRepeatHeadCode(33, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17528  Utilities->Format96HHMM(GetRepeatTime(24, ActionVectorEntry.EventTime, y, IncMinutes));
17529  }
17530  else if(ActionVectorEntry.Command == "Sns")
17531  {
17532  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
17533  TimeStr = GetRepeatHeadCode(34, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17534  Utilities->Format96HHMM(GetRepeatTime(25, ActionVectorEntry.EventTime, y, IncMinutes));
17535  }
17536  else if(ActionVectorEntry.FormatType == SNSShuttle)
17537  {
17538  if(y == 0) // first entry from shuttle
17539  {
17540  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
17541  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " +
17542  Utilities->Format96HHMM(GetRepeatTime(27, ActionVectorEntry.EventTime, y, IncMinutes));
17543  }
17544  else
17545  {
17546  PartStr = "Repeat shuttle service at " + ActionVectorEntry.LocationName + " from ";
17547  TimeStr = GetRepeatHeadCode(35, ActionVectorEntry.OtherHeadCode, y - 1, IncDigits) + " at " +
17548  Utilities->Format96HHMM(GetRepeatTime(22, ActionVectorEntry.EventTime, y, IncMinutes));
17549  } // y-1 for headcode above since it is the last repeat value that the train is from
17550 
17551  }
17552  else if(ActionVectorEntry.FormatType == SNSNonRepeatFromShuttle)
17553  {
17554  PartStr = "New service at " + ActionVectorEntry.LocationName + " from";
17555  // need repeat for the non-repeating headcode as it's the last train of the repeating shuttle
17556  TTrainDataEntry *TDE = ActionVectorEntry.LinkedTrainEntryPtr;
17557  AnsiString FirstHeadCode = TDE->HeadCode;
17558  int LastRepeatNumber = TDE->NumberOfTrains - 1;
17559  // a shuttle has to have at least 1 repeat
17560  int IncrementalDigits = TDE->ActionVector.at(TDE->ActionVector.size() - 1).FrontStartOrRepeatDigits;
17561  TimeStr = GetRepeatHeadCode(36, FirstHeadCode, LastRepeatNumber, IncrementalDigits) + " at " +
17562  Utilities->Format96HHMM(GetRepeatTime(23, ActionVectorEntry.EventTime, y, IncMinutes));
17563  }
17564  }
17565  else if(ActionVectorEntry.SequenceType == IntermediateSequence)
17566  {
17567  if(ActionVectorEntry.FormatType == TimeTimeLoc)
17568  {
17569  // here need 2 entries if times different so push the first right away & the second later
17570  // if times same just give the arrival entry
17571  if(ActionVectorEntry.DepartureTime != ActionVectorEntry.ArrivalTime)
17572  {
17573  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
17574  TimeStr = Utilities->Format96HHMM(GetRepeatTime(4, ActionVectorEntry.ArrivalTime, y, IncMinutes));
17575  OneTTEntry.Action = PartStr;
17576  OneTTEntry.Time = TimeStr;
17577  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
17578  PartStr = "Departs from " + ActionVectorEntry.LocationName;
17579  TimeStr = Utilities->Format96HHMM(GetRepeatTime(5, ActionVectorEntry.DepartureTime, y, IncMinutes));
17580  }
17581  else
17582  {
17583  PartStr = "Arrives & departs " + ActionVectorEntry.LocationName;
17584  TimeStr = Utilities->Format96HHMM(GetRepeatTime(29, ActionVectorEntry.ArrivalTime, y, IncMinutes));
17585  }
17586  }
17587  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime != TDateTime(-1)))
17588  {
17589  PartStr = "Arrives at " + ActionVectorEntry.LocationName;
17590  TimeStr = Utilities->Format96HHMM(GetRepeatTime(6, ActionVectorEntry.ArrivalTime, y, IncMinutes));
17591  }
17592  else if((ActionVectorEntry.FormatType == TimeLoc) && (ActionVectorEntry.ArrivalTime == TDateTime(-1)))
17593  {
17594  PartStr = "Departs from " + ActionVectorEntry.LocationName;
17595  TimeStr = Utilities->Format96HHMM(GetRepeatTime(7, ActionVectorEntry.DepartureTime, y, IncMinutes));
17596  }
17597  else if(ActionVectorEntry.FormatType == PassTime)
17598  {
17599  PartStr = "Passes " + ActionVectorEntry.LocationName;
17600  TimeStr = Utilities->Format96HHMM(GetRepeatTime(8, ActionVectorEntry.EventTime, y, IncMinutes));
17601  }
17602  else if(ActionVectorEntry.Command == "jbo")
17603  {
17604  PartStr = "Joined at " + ActionVectorEntry.LocationName + " by";
17605  TimeStr = GetRepeatHeadCode(37, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17606  Utilities->Format96HHMM(GetRepeatTime(9, ActionVectorEntry.EventTime, y, IncMinutes));
17607  }
17608  else if(ActionVectorEntry.Command == "fsp")
17609  {
17610  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
17611  {
17612  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
17613  }
17614  else
17615  {
17616  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
17617  }
17618  TimeStr = GetRepeatHeadCode(38, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17619  Utilities->Format96HHMM(GetRepeatTime(10, ActionVectorEntry.EventTime, y, IncMinutes));
17620  }
17621  else if(ActionVectorEntry.Command == "rsp")
17622  {
17623  if(ActionVectorEntry.SplitDistribution != "") //new at v2.15.0
17624  {
17625  PartStr = "Splits from front [mass%-power% = " + ActionVectorEntry.SplitDistribution + "] at " + ActionVectorEntry.LocationName + " to form";
17626  }
17627  else
17628  {
17629  PartStr = "Splits from front [mass%-power% = 50-50] at " + ActionVectorEntry.LocationName + " to form";
17630  }
17631  TimeStr = GetRepeatHeadCode(39, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17632  Utilities->Format96HHMM(GetRepeatTime(11, ActionVectorEntry.EventTime, y, IncMinutes));
17633  }
17634  else if(ActionVectorEntry.Command == "cdt")
17635  {
17636  PartStr = "Changes direction at " + ActionVectorEntry.LocationName;
17637  TimeStr = Utilities->Format96HHMM(GetRepeatTime(12, ActionVectorEntry.EventTime, y, IncMinutes));
17638  }
17639  else if(ActionVectorEntry.Command == "dsc")
17640  {
17641  PartStr = "Changes description at " + ActionVectorEntry.LocationName;
17642  TimeStr = Utilities->Format96HHMM(GetRepeatTime(76, ActionVectorEntry.EventTime, y, IncMinutes));
17643  }
17644  }
17645  else if(ActionVectorEntry.SequenceType == FinishSequence)
17646  {
17647  if(ActionVectorEntry.Command == "Fns")
17648  {
17649  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17650  TimeStr = GetRepeatHeadCode(40, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17651  Utilities->Format96HHMM(GetRepeatTime(13, ActionVectorEntry.EventTime, y, IncMinutes));
17652  }
17653  else if(ActionVectorEntry.Command == "F-nshs")
17654  {
17655  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17656  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
17657  (GetRepeatTime(17, ActionVectorEntry.EventTime, y, IncMinutes));
17658  }
17659  else if((ActionVectorEntry.Command == "Fns-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
17660  {
17661  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service ";
17662  TimeStr = GetRepeatHeadCode(41, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
17663  Utilities->Format96HHMM(GetRepeatTime(14, ActionVectorEntry.EventTime, y, IncMinutes));
17664  // y+1 because it's the NEXT service repeat number that is relevant
17665  }
17666  else if((ActionVectorEntry.Command == "Fns-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
17667  {
17668  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17669  TimeStr = ActionVectorEntry.NonRepeatingShuttleLinkHeadCode + " at " + Utilities->Format96HHMM
17670  (GetRepeatTime(15, ActionVectorEntry.EventTime, y, IncMinutes));
17671  }
17672  else if((ActionVectorEntry.Command == "Frh-sh") && (y < (TrainDataEntry.NumberOfTrains - 1)))
17673  {
17674  PartStr = "At " + ActionVectorEntry.LocationName + " forms new service";
17675  TimeStr = GetRepeatHeadCode(43, ActionVectorEntry.OtherHeadCode, y + 1, IncDigits) + " at " +
17676  Utilities->Format96HHMM(GetRepeatTime(16, ActionVectorEntry.EventTime, y, IncMinutes));
17677  // y+1 because it's the NEXT service repeat number that is relevant
17678  }
17679  else if((ActionVectorEntry.Command == "Frh-sh") && (y >= (TrainDataEntry.NumberOfTrains - 1)))
17680  {
17681  PartStr = "Terminates shuttle service at " + ActionVectorEntry.LocationName;
17682  // only used in chronological tt
17683  TimeStr = "End at " + Utilities->Format96HHMM(GetRepeatTime(28, ActionVectorEntry.EventTime, y, IncMinutes));
17684  // the "End at " is stripped out of the chronological tt but displayed in the traditional tt
17685  }
17686  else if(ActionVectorEntry.Command == "Frh")
17687  {
17688  PartStr = "Terminates at " + ActionVectorEntry.LocationName;
17689  // need here to examine the time of the preceding entry, may be ArrivalTime if TimeLoc, or EventTime otherwise
17690  if(z > 0)
17691  // should be for finish entry but include check for safety
17692  {
17693  if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
17694  {
17695  TimeStr = Utilities->Format96HHMM(GetRepeatTime(30, ActionVector.at(z - 1).EventTime, y, IncMinutes));
17696  }
17697  else if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
17698  {
17699  TimeStr = Utilities->Format96HHMM(GetRepeatTime(31, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
17700  }
17701  else
17702  {
17703  TimeStr = " "; // shouldn't ever get here
17704  }
17705  }
17706  }
17707  else if(ActionVectorEntry.Command == "Fer")
17708  {
17709  AnsiString AllowedExits;
17710  PartStr = "Exits railway" + GetExitLocationAndAt(0, ActionVectorEntry.ExitList, AllowedExits) + AllowedExits;
17711  TimeStr = Utilities->Format96HHMM(GetRepeatTime(18, ActionVectorEntry.EventTime, y, IncMinutes));
17712  }
17713  else if(ActionVectorEntry.Command == "Fjo")
17714  {
17715  PartStr = "At " + ActionVectorEntry.LocationName + " joins";
17716  TimeStr = GetRepeatHeadCode(44, ActionVectorEntry.OtherHeadCode, y, IncDigits) + " at " +
17717  Utilities->Format96HHMM(GetRepeatTime(19, ActionVectorEntry.EventTime, y, IncMinutes));
17718  }
17719  }
17720  else if(ActionVectorEntry.SequenceType == SequTypeForRepeatEntry)
17721  {
17722  continue; // no entry needed for a repeat
17723  }
17724  OneTTEntry.Action = PartStr;
17725  OneTTEntry.Time = TimeStr;
17726  OneTTTrain.OneFormattedTrainVector.push_back(OneTTEntry);
17727  // one per action
17728  }
17729  OneTTLine.OneCompleteFormattedTrainVector.push_back(OneTTTrain);
17730  // one per repeat
17731  }
17732  AllTTTrains->push_back(OneTTLine); // one per repeating train
17733  }
17734  // AllTTTrains vector now complete
17735 
17736  std::ofstream TTFile(TTFileName.c_str()); //formatted timetable
17737 
17738  if(TTFile == 0)
17739  {
17740  StopTTClockMessage(64, "Formatted timetable file failed to open - can't be created");
17741  delete AllTTTrains;
17742  Utilities->CallLogPop(1567);
17743  return;
17744  }
17745 /* formatted timetable types
17746  class TOneTrainFormattedEntry
17747  {
17748  AnsiString Action;//includes location if relevant
17749  AnsiString Time;
17750  };
17751 
17752  typedef std::vector<TOneTrainFormattedEntry> TOneFormattedTrainVector;
17753 
17754  class TOneCompleteFormattedTrain//headcode + list of actions
17755  {
17756  public:
17757  AnsiString HeadCode;
17758  TOneFormattedTrainVector OneFormattedTrainVector;
17759  };
17760 
17761  typedef std::vector<TOneCompleteFormattedTrain> TOneCompleteFormattedTrainVector;//list af all repeats
17762 
17763  class TTrainFormattedInformation//contains all information for a single TT entry (including repeats)
17764  {
17765  public:
17766  AnsiString Header;//description, mass, power, brake rate etc
17767  int NumberOfTrains;// number of repeats + 1
17768  TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector;//list af all repeats
17769  };
17770 
17771  typedef std::vector<TTrainFormattedInformation> TAllFormattedTrains;//all timetable in formatted form
17772  //end of formatted timetable types
17773 */
17774 
17775  // new layout using multiple rows
17776  TTFile << TableTitle.c_str() << '\n' << '\n';
17777  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
17778  {
17779  TTFile << AllTTTrains->at(x).Header.c_str();
17780  TTFile << '\n';
17781  TTFile << ','; // for the blank line above the action list
17782  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
17783  {
17784  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
17785  {
17786  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str() << ',';
17787  }
17788  else
17789  {
17790  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode.c_str();
17791  }
17792  }
17793  TTFile << '\n' << '\n';
17794 
17795  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.size(); z++)
17796  {
17797  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(0).OneFormattedTrainVector.at(z).Action.c_str() << ',';
17798  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
17799  {
17800  if(y < (AllTTTrains->at(x).NumberOfTrains - 1))
17801  {
17802  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str() << ',';
17803  }
17804  else
17805  {
17806  TTFile << AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time.c_str();
17807  }
17808  }
17809  TTFile << '\n';
17810  }
17811  TTFile << '\n' << '\n';
17812  }
17813 
17814  TTFile.close();
17815 
17816  AnsiString TTFileName2 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
17817 
17818  TTFileName2 = CurDir + "\\Formatted timetables\\Timetable " + TTFileName2 + "; " + RailwayTitle + "; " + TimetableTitle + ".txt";
17819 
17820  std::ofstream TTFile2(TTFileName2.c_str()); //chronological timetable
17821 
17822  if(TTFile2 == 0)
17823  {
17824  StopTTClockMessage(67, "Chronological timetable file failed to open - can't be created");
17825  delete AllTTTrains;
17826  Utilities->CallLogPop(1710);
17827  return;
17828  }
17829  typedef std::multimap<AnsiString, AnsiString>TAnsiMultiMap;
17830  std::multimap<AnsiString, AnsiString>::iterator AMMIT;
17831  std::pair<AnsiString, AnsiString>AnsiMultiMapEntry;
17832 
17833  TAnsiMultiMap *TAMM = new TAnsiMultiMap;
17834  LastTTTime = ""; //records the very last time in the timetable - used in analysis file for Frh entries
17835 
17836  // multimap of AnsiStrings with TimeString as key (to sort automatically)
17837 
17838  TTFile2 << TableTitle.c_str() << '\n' << '\n';
17839  for(unsigned int x = 0; x < AllTTTrains->size(); x++)
17840  {
17841  for(int y = 0; y < AllTTTrains->at(x).NumberOfTrains; y++) // number of repeating trains
17842  {
17843  for(unsigned int z = 0; z < AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.size(); z++)
17844  {
17845  bool GiveMessagesFalse = false;
17846  AnsiString TimeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Time;
17847  AnsiString HeadCodeString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).HeadCode;
17848  AnsiString ActionString = AllTTTrains->at(x).OneCompleteFormattedTrainVector.at(y).OneFormattedTrainVector.at(z).Action;
17849  if(CheckHeadCodeValidity(11, GiveMessagesFalse, TimeString.SubString(1, 4)))
17850  // 'NXNN at HH:MM' (will return true if H/C as integ check passed)
17851  {
17852  // fails for HH:MM because of ':' or 'End at HH:MM' because of ' '
17853  AnsiString OtherHeadCode = TimeString.SubString(1, 4);
17854  TimeString = TimeString.SubString(9, 5);
17855  ActionString += " " + OtherHeadCode;
17856  }
17857  if(TimeString.SubString(1, 7) == "End at ")
17858  // for Frh-sh final entry
17859  {
17860  TimeString = TimeString.SubString(8, 5);
17861  }
17862  AnsiString OneLine = TimeString + ' ' + HeadCodeString + ' ' + ActionString + '\n';
17863  AnsiMultiMapEntry.first = TimeString;
17864  AnsiMultiMapEntry.second = OneLine;
17865  TAMM->insert(AnsiMultiMapEntry);
17866  }
17867  }
17868  }
17869 
17870  for(AMMIT = TAMM->begin(); AMMIT != TAMM->end(); AMMIT++)
17871  {
17872  TTFile2 << (AMMIT->second).c_str();
17873  }
17874  delete AllTTTrains;
17875  delete TAMM;
17876  TTFile2.close();
17877  Utilities->CallLogPop(1580);
17878 }
17879 
17880 // ---------------------------------------------------------------------------
17881 
17882 bool TTrainController::CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked,
17883  bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
17884 {
17885 
17886  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CreateTTAnalysisFile");
17887  bool AnalysisError = false;
17888  AnsiString SequenceLog = "SequenceLog\n";
17889 
17890 /* Double crosslink (shuttle) table:
17891 
17892 Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
17893  Code ShuttleLink- EntryPtr ShuttleLink-
17894  HeadCode EntryPtr
17895 
17896 Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
17897 Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
17898 F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
17899 Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
17900 Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
17901 Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
17902 
17903 Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
17904 */
17905 
17906  try
17907  {
17908  //New section at v2.5.0 for tt conflict analysis
17909  /*
17910  typedef std::list<AnsiString> TServiceCallingLocsList;
17911  typedef std::map<AnsiString, TServiceCallingLocsList> TAllServiceCallingLocsMap;
17912 
17914  struct TLocServiceTimes
17915  {
17916  AnsiString Location;
17917  AnsiString ServiceAndRepeatNum;
17918  AnsiString AtLocTime;
17919  AnsiString ArrTime;
17920  AnsiString DepTime;
17921  AnsiString FrhMarker;
17922  };
17923  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
17924  */
17925 
17926  //first have to check through all the services and give each one a unique name, or the analysis won't recognise differences between services that have the same reference
17927  //to do that need a new TrainDataVector as don't want to change anything in the original. TrainDataVectorCopy is used for building AllServiceCallingLocsMap & LocServiceTimesVector
17928 
17929 //create TrainDataVectorCopy and populate service refs with /1, /2 etc
17930  TrainDataVectorCopy = TrainDataVector; //don't need it on heap as TrainController is on the heap. Didn't need others in CreatFormattedTimetables but leave as is.
17931  TTrainDataVector::iterator TDVIt, TDVCopyIt;
17932  int Suffix = 0;
17933  int IteratorNumber = 0;
17934  AnsiString AnsiSuffix = "";
17935  for(TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end() - 1; TDVIt++)
17936  {
17937  IteratorNumber++; //first value in loop is 1
17938  Suffix = 0;
17939  for(TDVCopyIt = TrainDataVectorCopy.begin() + IteratorNumber; TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
17940  {
17941  if(TDVCopyIt->HeadCode == TDVIt->HeadCode)
17942  {
17943  Suffix++; //first value is 1
17944  AnsiSuffix = AnsiString(Suffix);
17945  TDVCopyIt->ServiceReference = TDVIt->HeadCode + "/" + AnsiSuffix; //set both the HeadCode + any forward slashes and numbers, this is because sometimes
17946  TDVCopyIt->HeadCode = TDVIt->HeadCode + "/" + AnsiSuffix; //service refs are used and sometimes H/Cs, so need them to be the same,
17947  } //they are all unique at this point anyway
17948  }
17949  }
17950 //now make all linked pointers in ActionVectorEntries point to links in the vector copy (still point to original vector at this stage)
17951 //and set the linked headcodes to the correct values - all unique at this point
17952  int Increment = 0, SlashPos;
17953  TActionVectorIterator AVEIt;
17954  AnsiString LinkedHeadCode;
17955 
17956  for(TDVCopyIt = TrainDataVectorCopy.begin(); TDVCopyIt != TrainDataVectorCopy.end(); TDVCopyIt++)
17957  {
17958  for(AVEIt = TDVCopyIt->ActionVector.begin(); AVEIt != TDVCopyIt->ActionVector.end(); AVEIt++)
17959  {
17960  if(AVEIt->LinkedTrainEntryPtr != NULL)
17961  {
17962  Increment = AVEIt->LinkedTrainEntryPtr - &TrainDataVector.at(0);
17963  AVEIt->LinkedTrainEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
17964  //now set AVEIt->OtherHeadCode to the linked headcodes with /1, /2 etc
17965  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
17966  LinkedHeadCode = (*AVEIt->LinkedTrainEntryPtr).ServiceReference;
17967  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
17968  SlashPos = 0;
17969  for(int x = LinkedHeadCode.Length(); x > 0; x--)
17970  {
17971  if(LinkedHeadCode[x] == '/')
17972  {
17973  SlashPos = LinkedHeadCode.Length() - x + 1;
17974  break;
17975  }
17976  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
17977  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
17978  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
17979  {
17980  break;
17981  }
17982  }
17983  //now strip off any prefix
17984  AVEIt->OtherHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
17985  }
17986  else
17987  {
17988  AVEIt->OtherHeadCode = "";
17989  }
17990  if(AVEIt->NonRepeatingShuttleLinkEntryPtr != NULL)
17991  {
17992  Increment = AVEIt->NonRepeatingShuttleLinkEntryPtr - &TrainDataVector.at(0);
17993  AVEIt->NonRepeatingShuttleLinkEntryPtr = &TrainDataVectorCopy.at(0) + Increment;
17994  //now set AVEIt->NonRepeatingShuttleLinkHeadCode to the linked headcodes with /1, /2 etc
17995  //but note that these ARE HeadCodes and not service refs, so need to strip off any prefixes from the linked service refs
17996  LinkedHeadCode = (*AVEIt->NonRepeatingShuttleLinkEntryPtr).ServiceReference;
17997  //now count from back until reach a '/' character or a non-integer character, if reach non-integer first then no '/' present at end of H/c (but may be one earlier as a prefix)
17998  SlashPos = 0;
17999  for(int x = LinkedHeadCode.Length(); x > 0; x--)
18000  {
18001  if(LinkedHeadCode[x] == '/')
18002  {
18003  SlashPos = LinkedHeadCode.Length() - x + 1;
18004  break;
18005  }
18006  else if((LinkedHeadCode[x] != '0') && (LinkedHeadCode[x] != '1') && (LinkedHeadCode[x] != '2') && (LinkedHeadCode[x] != '3') &&
18007  (LinkedHeadCode[x] != '4') && (LinkedHeadCode[x] != '5') && (LinkedHeadCode[x] != '6') && (LinkedHeadCode[x] != '7') &&
18008  (LinkedHeadCode[x] != '8') && (LinkedHeadCode[x] != '8'))
18009  {
18010  break;
18011  }
18012  }
18013  //now strip off any prefix
18014  AVEIt->NonRepeatingShuttleLinkHeadCode = LinkedHeadCode.SubString(LinkedHeadCode.Length() - 3 - SlashPos, 4 + SlashPos);
18015  }
18016  else
18017  {
18018  AVEIt->NonRepeatingShuttleLinkHeadCode = "";
18019  }
18020  }
18021  }
18022  //from here only TrainDataVectorCopy used
18023  SequenceLog += "1\n";
18024  //build AllServiceCallingLocsMap, it only uses the base service reference (with /1, /2 etc suffixes) as later times are calculated from the repeat number
18025  TServiceCallingLocsList ServiceCallingLocsList;
18026  std::pair<AnsiString, TServiceCallingLocsList> AllServiceCallingLocsEntry;
18027  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18028  {
18029  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18030  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18031  AllServiceCallingLocsEntry.first = TrainDataEntry.ServiceReference;
18032  ServiceCallingLocsList.clear();
18033  if(ActionVector.empty())
18034  {
18035  continue;
18036  }
18037  if(ActionVector.at(0).SignallerControl)
18038  {
18039  continue;
18040  }
18041  for(unsigned int z = 0; z < ActionVector.size(); z++)
18042  {
18043  TActionVectorEntry AVE = ActionVector.at(z);
18044  if(AVE.FormatType == StartNew)
18045  {
18046  if(AVE.LocationType == AtLocation) //located Snt
18047  {
18048  ServiceCallingLocsList.push_back(AVE.LocationName);
18049  }
18050  else //unlocated Snt (could be entering at continuation)
18051  {
18053  if(TE.ActiveTrackElementName != "")
18054  {
18055  ServiceCallingLocsList.push_back(TE.ActiveTrackElementName);
18056  }
18057  else
18058  {
18059  int HLoc = TE.HLoc;
18060  int VLoc = TE.VLoc;
18061  AnsiString HString;
18062  AnsiString VString;
18063  if(HLoc < 0)
18064  {
18065  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18066  }
18067  else
18068  {
18069  HString = AnsiString(HLoc);
18070  }
18071  if(VLoc < 0)
18072  {
18073  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18074  }
18075  else
18076  {
18077  VString = AnsiString(VLoc);
18078  }
18079  ServiceCallingLocsList.push_back(HString + '-' + VString);
18080  }
18081  }
18082  }
18083  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18084  {
18085  ServiceCallingLocsList.push_back(AVE.LocationName);
18086  }
18087  else if(AVE.FormatType == TimeLoc) //z must be > 0
18088  {
18089  if(ServiceCallingLocsList.back() != AVE.LocationName)
18090  {
18091  ServiceCallingLocsList.push_back(AVE.LocationName); //may be listed twice in succession so only want one entry
18092  }
18093  }
18094  else if(AVE.FormatType == PassTime)
18095  {
18096  ServiceCallingLocsList.push_back(AVE.LocationName);
18097  }
18098  else if(AVE.FormatType == TimeTimeLoc)
18099  {
18100  ServiceCallingLocsList.push_back(AVE.LocationName);
18101  }
18102  else if(AVE.Command == "cdt") //list if not next to start or finish
18103  {
18104  if(ActionVector.at(z-1).SequenceType == StartSequence)
18105  {
18106  continue;
18107  }
18108  else if(ActionVector.at(z+1).SequenceType == FinishSequence) //although deal with Fer entries cdt (train stopped) can't precede FER (train moving)
18109  {
18110  continue;
18111  }
18112  else
18113  {
18114  AnsiString TimeString = Utilities->Format96HHMM(AVE.EventTime);
18115  ServiceCallingLocsList.push_back("%%%" + TimeString); //%%% is a marker - unlikely that any locations will begin with this & easy to check to identify a time
18116  }
18117  }
18118  else if(AVE.FormatType == ExitRailway) //Fer
18119  {
18120  TTrackElement TE = Track->TrackElementAt(995, AVE.ExitList.front());
18121  AnsiString LName = TE.ActiveTrackElementName;
18122  if(LName != "")
18123  {
18124  ServiceCallingLocsList.push_back(LName);
18125  }
18126  else
18127  {
18128  int HLoc = TE.HLoc;
18129  int VLoc = TE.VLoc;
18130  AnsiString HString;
18131  AnsiString VString;
18132  if(HLoc < 0)
18133  {
18134  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18135  }
18136  else
18137  {
18138  HString = AnsiString(HLoc);
18139  }
18140  if(VLoc < 0)
18141  {
18142  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18143  }
18144  else
18145  {
18146  VString = AnsiString(VLoc);
18147  }
18148  ServiceCallingLocsList.push_back(HString + '-' + VString);
18149  }
18150  }
18151  }
18152  AllServiceCallingLocsEntry.second = ServiceCallingLocsList;
18153  AllServiceCallingLocsMap.insert(AllServiceCallingLocsEntry);
18154  }
18155  //AllServiceCallingLocsMap built
18156  SequenceLog += "2\n";
18157 /*
18158 // this sequence is to test the validity of AllServiceCallingLocsMap
18159  AnsiString TestFile = CurDir + "\\Formatted timetables\\TestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18160  std::ofstream Test(TestFile.c_str());
18161 
18162  if(TestFile == 0)
18163  {
18164  ShowMessage("TestFile failed to open - can't be created");
18165  Utilities->CallLogPop();
18166  return false;
18167  }
18168 
18169  for(TAllServiceCallingLocsMap::iterator ASCLIt = AllServiceCallingLocsMap.begin(); ASCLIt != AllServiceCallingLocsMap.end(); ASCLIt++)
18170  {
18171  Test << ASCLIt->first << '\n'; //service ref
18172  for(TServiceCallingLocsList::iterator SCLIt = ASCLIt->second.begin(); SCLIt != ASCLIt->second.end(); SCLIt++)
18173  {
18174  Test << *SCLIt << '\n';
18175  }
18176  Test << "\n\n";
18177  }
18178  Test.close();
18179  Utilities->CallLogPop();
18180  return true;
18181 */
18182  //initialise variables before calc LastTTTime & build LocServiceTimesVector
18183  if(TrainDataVectorCopy.empty())
18184  {
18185  ShowMessage("Unable to create a program-readable timetable - please check the timetable file validity");
18186  Utilities->CallLogPop(2209);
18187  return(false);
18188  }
18189  TLocServiceTimes TLSTEntry;
18190  TLocServiceTimesVector LocServiceTimesVector; //will be on heap as TrainController is on the heap
18191  bool NumPlatsAtThisLocCalculated = false, ArrivalsPrinted = false, DeparturesPrinted = false, AtLocsPrinted = false;
18192  AnsiString PreviousService = "", PreviousServiceAndRepeatNumTotalOutput = "", BasicTime = "", MinuteString = "", LastAnsiTime = "";
18193  int NumTrains = 0, NumPlats = 0, LastFrhCount = 0, FrhCount = 0, NumTrainsAtLoc = 0;
18194  LastTTTime = "";
18195  SequenceLog += "3\n";
18196  //calculate LastTTTime
18197  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18198  {
18199  TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18200  TActionVector &ActionVector = TrainDataEntry.ActionVector;
18201  TActionVectorIterator AVLast = ActionVector.end() - 1; //points to last entry
18202  TDateTime LastTDTime;
18203  int IncMinutes = 0;
18204  NumTrains = TrainDataEntry.NumberOfTrains;
18205  if(ActionVector.empty())
18206  {
18207  continue;
18208  }
18209  if(ActionVector.at(0).SignallerControl)
18210  {
18211  continue;
18212  }
18213  if(AVLast->FormatType == Repeat)
18214  {
18215  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18216  AVLast--; //now points to the command before the repeat
18217  }
18218  if(AVLast->FormatType == FinRemHere) //not 'else if' as may have both a repeat and an Frh
18219  {
18220  AVLast--; //points to last timed entry
18221  }
18222  //here AVLast points to last entry with a time
18223  if(AVLast->ArrivalTime != TDateTime(-1))
18224  {
18225  LastTDTime = AVLast->ArrivalTime;
18226  }
18227  else if(AVLast->EventTime != TDateTime(-1)) //can't be a departure time
18228  {
18229  LastTDTime = AVLast->EventTime;
18230  }
18231  else
18232  {
18233  continue; //shouldn't ever reach here but if do then skip this service
18234  }
18235  if(NumTrains == 1)
18236  {
18237  LastAnsiTime = Utilities->Format96HHMM(LastTDTime);
18238  }
18239  else
18240  {
18241  LastAnsiTime = Utilities->Format96HHMM(GetRepeatTime(59, LastTDTime, NumTrains - 1, IncMinutes));
18242  }
18243  if(LastAnsiTime > LastTTTime)
18244  {
18245  LastTTTime = LastAnsiTime;
18246  }
18247  }
18248  SequenceLog += "4\n";
18249 //build LocServiceTimesVector
18250 
18251 /*
18252  struct TLocServiceTimes
18253  {
18254  AnsiString Location;
18255  AnsiString ServiceAndRepeatNum;
18256  AnsiString AtLocTime;
18257  AnsiString ArrTime;
18258  AnsiString DepTime;
18259  AnsiString FrhMarker;
18260  };
18261  typedef std::vector<TLocServiceTimes> TLocServiceTimesVector;
18262 
18263 This works as follows:
18264 ServiceAndRepeatNum is taken from the TrainDataVectorCopy as it is the same for all actionvector entries
18265 Location is taken from ActionVectorEntry.LocationName if there is one, or from the H & V locations if not (e.g. at an unnamed Fer)
18266 AtLocTime is always entered either on its own or with ArrTime or DepTime as appropriate
18267 
18268 Every action for every train is examined and times entered as follows:-
18269 a) a located Snt: entry time becomes the AtLocTime, and all subsequent minutes entered too up to but not including a departure or a finish
18270 b) an unlocated Snt: entry time becomes DepTime
18271 c) all other start entries: entry time becomes AtLoc, and all subsequent minutes entered too up to but not including a departure or a finish
18272 d) TimeLoc Arr: entry time becomes ArrTime, and all subsequent minutes entered too up to but not including a departure or a finish
18273 e) TimeLoc Dep: entry time becomes DepTime, checks if DepTime same as earlier ArrTime and if so all times go in as one entry
18274 f) TimeTimeLoc: Arrival time entered as ArrTime, a check if Arr & Dep same and if so go in as one entry, else all minutes between entered as AtLocs then DepTime
18275 g) ExitRailway (Fer): check if located and use LocationName if so. else use H & V positions, time becomes AtLocTime
18276 h) Frh: use the earlier vector time as the AtLocTime and set FrhMarker, and enter all minutes to end of timetable as AtLocs
18277 i) Frh-sh: for the last train use time as AtLocTime, set FrhMarker, and enter all minutes to end of timetable as AtLocs
18278 j) all other finish entries (all link to another service) are ignored as will be listed for the linked service
18279 */
18280  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
18281  {
18282  const TTrainDataEntry &TrainDataEntry = TrainDataVectorCopy.at(x);
18283  const TActionVector &ActionVector = TrainDataEntry.ActionVector;
18284  AnsiString ServiceRef = TrainDataEntry.ServiceReference;
18285  int IncMinutes = 0;
18286  NumTrains = TrainDataEntry.NumberOfTrains;
18287  if(ActionVector.empty())
18288  {
18289  continue;
18290  }
18291  if(ActionVector.at(0).SignallerControl)
18292  {
18293  continue;
18294  }
18295  if(ActionVector.at(ActionVector.size() - 1).FormatType == Repeat)
18296  {
18297  IncMinutes = ActionVector.at(ActionVector.size() - 1).RearStartOrRepeatMins;
18298  }
18299  for(int y = 0; y < NumTrains; y++) //y is the repeat number
18300  {
18301  if(NumTrains == 1)
18302  {
18303  TLSTEntry.ServiceAndRepeatNum = ServiceRef;
18304  }
18305  else if(y == 0)
18306  {
18307  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (First service)";
18308  }
18309  else
18310  {
18311  TLSTEntry.ServiceAndRepeatNum = ServiceRef + " (Repeat " + AnsiString(y) + ")";
18312  }
18313  for(unsigned int z = 0; z < ActionVector.size(); z++)
18314  {
18315  TActionVectorEntry AVE = ActionVector.at(z);
18316  TLSTEntry.AtLocTime = "";
18317  TLSTEntry.ArrTime = "";
18318  TLSTEntry.DepTime = "";
18319  TLSTEntry.Location = "";
18320  TLSTEntry.FrhMarker = "";
18321 
18322  if(AVE.FormatType == StartNew) //Snt only
18323  {
18324  if(AVE.LocationType == AtLocation) //located Snt, class time as AtLocTime
18325  {
18326  TLSTEntry.Location = AVE.LocationName;
18327  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(58, AVE.EventTime, y, IncMinutes));
18328  LocServiceTimesVector.push_back(TLSTEntry);
18329 
18330  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18331  AnsiString IncTime = "", FoundStopTime = ""; //these handled in later checks
18332  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18333  {
18334  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18335  {
18336  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(62, ActionVector.at(a).DepartureTime, y, IncMinutes));
18337  break;
18338  }
18339  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
18340  {
18341  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(63, ActionVector.at(a).EventTime, y, IncMinutes));
18342  break;
18343  }
18344  }
18345  if(FoundStopTime == "")
18346  {
18347  throw Exception("Failure to determine FoundStopTime for located Snt");
18348  }
18349  int WhileCount = 0;
18350  while(true)
18351  {
18352  //add minutes until reach FoundStopTime but don't add that time
18353  WhileCount++;
18354  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18355  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18356  TLSTEntry.DepTime = "";
18357  TLSTEntry.ArrTime = "";
18358  if(IncTime >= FoundStopTime) //don't add that time
18359  {
18360  break;
18361  }
18362  LocServiceTimesVector.push_back(TLSTEntry);
18363  if(WhileCount > 2000)
18364  {
18365  throw Exception("While loop failed to break in 2000 loops for located Snt");
18366  }
18367  }
18368  }
18369  else //unlocated Snt, use the EventTime as DepTime for this vector
18370  {
18372  if(TE.ActiveTrackElementName != "")
18373  {
18374  TLSTEntry.Location = TE.ActiveTrackElementName;
18375  }
18376  else
18377  {
18378  int HLoc = TE.HLoc;
18379  int VLoc = TE.VLoc;
18380  AnsiString HString;
18381  AnsiString VString;
18382  if(HLoc < 0)
18383  {
18384  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18385  }
18386  else
18387  {
18388  HString = AnsiString(HLoc);
18389  }
18390  if(VLoc < 0)
18391  {
18392  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18393  }
18394  else
18395  {
18396  VString = AnsiString(VLoc);
18397  }
18398  TLSTEntry.Location = HString + '-' + VString;
18399  }
18400  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(49, AVE.EventTime, y, IncMinutes));
18401  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
18402  LocServiceTimesVector.push_back(TLSTEntry);
18403  }
18404  }
18405 
18406  else if(AVE.SequenceType == StartSequence) //other start entries, all located
18407  {
18408  TLSTEntry.Location = AVE.LocationName;
18409  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(50, AVE.EventTime, y, IncMinutes));
18410  LocServiceTimesVector.push_back(TLSTEntry);
18411  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18412  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
18413  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18414  {
18415  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18416  {
18417  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(64, ActionVector.at(a).DepartureTime, y, IncMinutes));
18418  break;
18419  }
18420  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered in a later test
18421  {
18422  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(65, ActionVector.at(a).EventTime, y, IncMinutes));
18423  break;
18424  }
18425  }
18426  if(FoundStopTime == "")
18427  {
18428  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
18429  }
18430  int WhileCount = 0;
18431  while(true)
18432  {
18433  //add minutes until reach FoundStopTime but don't add that time
18434  WhileCount++;
18435  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18436  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18437  TLSTEntry.DepTime = "";
18438  TLSTEntry.ArrTime = "";
18439  if(IncTime >= FoundStopTime) //don't add that time
18440  {
18441  break;
18442  }
18443  LocServiceTimesVector.push_back(TLSTEntry);
18444  if(WhileCount > 2000)
18445  {
18446  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
18447  }
18448  }
18449  }
18450 
18451  else if(AVE.FormatType == TimeLoc) //could be arr or dep, if arrival add in all mins to the departure or finish
18452  {
18453  TLSTEntry.Location = AVE.LocationName;
18454  if(AVE.ArrivalTime > TDateTime(-1)) //one or other set, not both, in this case arrival
18455  {
18456  bool SkipAddingMinutes = false;
18457  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(51, AVE.ArrivalTime, y, IncMinutes));
18458  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
18459  LocServiceTimesVector.push_back(TLSTEntry); //Arr and AtLoc added (may be popped if dep time found to be same at next TimeLoc)
18460  //now look forwards until find a departure or a Fns, Fns-sh etc & add in all the minutes up to but not including the dep or finish times
18461  AnsiString IncTime = "", FoundStopTime = ""; //these handled in other checks
18462  for(unsigned int a = z + 1; a < ActionVector.size(); a++)
18463  {
18464  if(ActionVector.at(a).FormatType == TimeLoc) //must be a departure
18465  {
18466  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(66, ActionVector.at(a).DepartureTime, y, IncMinutes));
18467  break;
18468  }
18469  if(ActionVector.at(a).SequenceType == FinishSequence) //finish catered for in a later test
18470  {
18471  FoundStopTime = Utilities->Format96HHMM(GetRepeatTime(67, ActionVector.at(a).EventTime, y, IncMinutes));
18472  if((a <= (z + 2)) && (FoundStopTime == TLSTEntry.ArrTime) && ((ActionVector.at(a).LinkedTrainEntryPtr != NULL) || (ActionVector.at(a).NonRepeatingShuttleLinkEntryPtr != NULL)))
18473  //finish immediately after arrival at same time, and a forward linked service. Added at v2.6.0 to prevent two linked trains being listed at same location
18474  //at v2.10.0 changed (a == z + 1) to (a <= (z + 2)) as can have a cdt between, this allows for that
18475  {
18476  LocServiceTimesVector.pop_back(); //pop the entry as the linked train will be listed at the relevant time and don't want to list both
18477  SkipAddingMinutes = true;
18478  }
18479  break;
18480  }
18481  }
18482  if(FoundStopTime == "")
18483  {
18484  throw Exception("Failure to determine FoundStopTime for SequenceType == StartSequence");
18485  }
18486  if(!SkipAddingMinutes)
18487  {
18488  int WhileCount = 0;
18489  while(true)
18490  {
18491  //add minutes until reach FoundStopTime but don't add that time
18492  WhileCount++;
18493  IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18494  TLSTEntry.AtLocTime = IncTime; //all entered times will be AtLocs
18495  TLSTEntry.DepTime = "";
18496  TLSTEntry.ArrTime = "";
18497  if(IncTime >= FoundStopTime) //don't add that time
18498  {
18499  break;
18500  }
18501  LocServiceTimesVector.push_back(TLSTEntry);
18502  if(WhileCount > 2000)
18503  {
18504  throw Exception("While loop failed to break in 2000 loops for SequenceType == StartSequence");
18505  }
18506  }
18507  }
18508  }
18509  else if(AVE.DepartureTime > TDateTime(-1)) //need to check if the arrival time (which should already be listed) is same and if so put all times on one line
18510  {
18511  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(52, AVE.DepartureTime, y, IncMinutes));
18512  TLSTEntry.AtLocTime = TLSTEntry.DepTime;
18513  if((TLSTEntry.Location == LocServiceTimesVector.back().Location) && (TLSTEntry.ServiceAndRepeatNum == LocServiceTimesVector.back().ServiceAndRepeatNum)) //if not it's a new service
18514  {
18515  if(TLSTEntry.DepTime == LocServiceTimesVector.back().ArrTime)
18516  {
18517  TLSTEntry.ArrTime = LocServiceTimesVector.back().ArrTime;
18518  LocServiceTimesVector.pop_back();
18519  LocServiceTimesVector.push_back(TLSTEntry); //Arr, Dep and AtLoc added in place of earlier Arr entry.
18520  }
18521  else //just add the dep & atloc times
18522  {
18523  TLSTEntry.ArrTime = "";
18524  LocServiceTimesVector.push_back(TLSTEntry);
18525  }
18526  }
18527  else //just add the dep & atloc times
18528  {
18529  TLSTEntry.ArrTime = "";
18530  LocServiceTimesVector.push_back(TLSTEntry);
18531  }
18532  }
18533  }
18534 
18535  else if(AVE.FormatType == TimeTimeLoc)
18536  {
18537  TLSTEntry.Location = AVE.LocationName;
18538  if(AVE.ArrivalTime > TDateTime(-1)) //should be
18539  {
18540  TLSTEntry.ArrTime = Utilities->Format96HHMM(GetRepeatTime(53, AVE.ArrivalTime, y, IncMinutes));
18541  TLSTEntry.AtLocTime = TLSTEntry.ArrTime;
18542  }
18543  if(AVE.DepartureTime > TDateTime(-1)) //should be
18544  {
18545  TLSTEntry.DepTime = Utilities->Format96HHMM(GetRepeatTime(54, AVE.DepartureTime, y, IncMinutes));
18546  }
18547  if(TLSTEntry.ArrTime == TLSTEntry.DepTime)
18548  {
18549  LocServiceTimesVector.push_back(TLSTEntry);
18550  }
18551  else
18552  {
18553  AnsiString TempDepTime = TLSTEntry.DepTime; //save it temporarily
18554  TLSTEntry.DepTime = "";
18555  LocServiceTimesVector.push_back(TLSTEntry); //push just the arrival and AtLoc times
18556  TLSTEntry.ArrTime = ""; //done with this now
18557  while(TLSTEntry.AtLocTime < TempDepTime)
18558  {
18559  TLSTEntry.AtLocTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18560  if(TLSTEntry.AtLocTime == TempDepTime)
18561  {
18562  TLSTEntry.DepTime = TempDepTime; //restore value
18563  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc and Dep times - will finish loop after this
18564  }
18565  else
18566  {
18567  LocServiceTimesVector.push_back(TLSTEntry); //push the AtLoc time on its own
18568  }
18569  }
18570  }
18571  }
18572 
18573  else if(AVE.FormatType == PassTime) //added at v2.9.1
18574  { //adds 2 entries, 1st with PassTime as ArrTime and AtLocTime, 2nd with PassTime as AtLocTime & DepTime
18575  TLSTEntry.Location = AVE.LocationName;;
18576  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(73, AVE.EventTime, y, IncMinutes));
18577  TLSTEntry.ArrTime = TLSTEntry.AtLocTime; //DepTime already set to null
18578  LocServiceTimesVector.push_back(TLSTEntry); //1st entry
18579  TLSTEntry.ArrTime = ""; //need to reset this to null
18580  TLSTEntry.DepTime = TLSTEntry.AtLocTime;
18581  LocServiceTimesVector.push_back(TLSTEntry); //2nd entry
18582  }
18583 
18584  else if(AVE.FormatType == ExitRailway) //Fer
18585  {
18586  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(55, AVE.EventTime, y, IncMinutes));
18587  //don't know which exit will be used during operation so use the first in ExitList, if several with different names then will
18588  //be wrong, but can't guess from here & most will have same name
18589  AnsiString LName = Track->TrackElementAt(990, AVE.ExitList.front()).ActiveTrackElementName;
18590  if(LName != "")
18591  {
18592  TLSTEntry.Location = LName;
18593  }
18594  else
18595  {
18596  int HLoc = Track->TrackElementAt(991, AVE.ExitList.front()).HLoc;
18597  int VLoc = Track->TrackElementAt(992, AVE.ExitList.front()).VLoc;
18598  AnsiString HString;
18599  AnsiString VString;
18600  if(HLoc < 0)
18601  {
18602  HString = AnsiString('N') + AnsiString(HLoc).SubString(2, AnsiString(HLoc).Length() - 1); //strip off '-'
18603  }
18604  else
18605  {
18606  HString = AnsiString(HLoc);
18607  }
18608  if(VLoc < 0)
18609  {
18610  VString = AnsiString('N') + AnsiString(VLoc).SubString(2, AnsiString(VLoc).Length() - 1); //strip off '-'
18611  }
18612  else
18613  {
18614  VString = AnsiString(VLoc);
18615  }
18616  TLSTEntry.Location = HString + '-' + VString;
18617  }
18618  LocServiceTimesVector.push_back(TLSTEntry); //just use the exit time as AtLocTime
18619  }
18620 
18621  else if(AVE.FormatType == FinRemHere) //Frh, not Frh-sh, that dealt with next
18622  {
18623  AnsiString FrhTime;
18624  if(ActionVector.at(z - 1).ArrivalTime != TDateTime(-1))
18625  {
18626  FrhTime = Utilities->Format96HHMM(GetRepeatTime(56, ActionVector.at(z - 1).ArrivalTime, y, IncMinutes));
18627  }
18628  else if(ActionVector.at(z - 1).EventTime != TDateTime(-1))
18629  {
18630  FrhTime = Utilities->Format96HHMM(GetRepeatTime(57, ActionVector.at(z - 1).EventTime, y, IncMinutes));
18631  }
18632  TLSTEntry.AtLocTime = FrhTime; //use the last entry time as the first recorded time
18633  TLSTEntry.Location = AVE.LocationName;
18634  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18635  TLSTEntry.FrhMarker = "Frh";
18636  LocServiceTimesVector.push_back(TLSTEntry);
18637  TLSTEntry.FrhMarker = "";
18638  //add all times from next minute to end of timetable
18639  while(IncTime <= LastTTTime)
18640  {
18641  TLSTEntry.AtLocTime = IncTime;
18642  LocServiceTimesVector.push_back(TLSTEntry);
18643  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
18644  }
18645  }
18646 
18647  else if(AVE.Command == "Frh-sh") //do nothing if links to other shuttle but treat as Frh when remaining here
18648  {
18649  if(y == NumTrains - 1) //last repeat, it remains here when accessed for the last train
18650  {
18651  TLSTEntry.AtLocTime = Utilities->Format96HHMM(GetRepeatTime(68, AVE.EventTime, y, IncMinutes));
18652  TLSTEntry.Location = AVE.LocationName;
18653  AnsiString IncTime = Utilities->IncrementAnsiTimeOneMinute(TLSTEntry.AtLocTime);
18654  TLSTEntry.FrhMarker = "Frh";
18655  LocServiceTimesVector.push_back(TLSTEntry);
18656  TLSTEntry.FrhMarker = "";
18657  //add all times from next minute to end of timetable
18658  while(IncTime <= LastTTTime)
18659  {
18660  TLSTEntry.AtLocTime = IncTime;
18661  LocServiceTimesVector.push_back(TLSTEntry);
18662  IncTime = Utilities->IncrementAnsiTimeOneMinute(IncTime);
18663  }
18664  }
18665  }
18666 
18667  else if(AVE.SequenceType == FinishSequence) //other finish types - all located & all link to another service
18668  {
18669  //nothing is done here as the entry will be listed at this time under the new service reference
18670  }
18671  }
18672  }
18673  }
18674  SequenceLog += "5\n";
18675  //now sort in location order
18676  std::sort(LocServiceTimesVector.begin(), LocServiceTimesVector.end(), &LocServiceTimesLocationSort); //LocServiceTimesLocationSort is a function pointer
18677  //LocServiceTimesVector now complete & sorted in location order
18678 
18679 /*
18680 //start of debugging section
18681 //create LocServiceTimesVector output file for debugging purposes
18682  AnsiString LSTVTestFile = CurDir + "\\Formatted timetables\\LSTVTestFile; " + RailwayTitle + "; " + TimetableTitle + ".txt";
18683  std::ofstream LSTVFile(LSTVTestFile.c_str());
18684  for(TLocServiceTimesVector::iterator LSTVIt = LocServiceTimesVector.begin(); LSTVIt != LocServiceTimesVector.end(); LSTVIt++)
18685  {
18686  LSTVFile << LSTVIt->Location + '\n';
18687  LSTVFile << LSTVIt->ServiceAndRepeatNum + '\n';
18688  LSTVFile << "AtLocTime = " + LSTVIt->AtLocTime + '\n';
18689  LSTVFile << "ArrTime = " + LSTVIt->ArrTime + '\n';
18690  LSTVFile << "DepTime = " + LSTVIt->DepTime + '\n';
18691  if(LSTVIt->FrhMarker == "")
18692  {
18693  LSTVFile << "Not Frh\n";
18694  }
18695  else
18696  {
18697  LSTVFile << LSTVIt->FrhMarker + '\n';
18698  }
18699  LSTVFile << '\n';
18700  }
18701  LSTVFile.close();
18702  Utilities->CallLogPop();
18703  return(true);
18704 //end of debugging section
18705 */
18706  //declare pointers for use in printouts
18707  TLocServiceTimesVector::iterator Ptr1, Ptr2;
18708 
18709  //set up the output file
18710  AnsiString TTFileName3 = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
18711  TTFileName3 = CurDir + "\\Formatted timetables\\Conflict Analysis " + TTFileName3 + "; " + RailwayTitle + "; " + TimetableTitle + ".csv";
18712 
18713  std::ofstream TTFile3(TTFileName3.c_str());
18714 
18715  if(TTFile3 == 0)
18716  {
18717  ShowMessage("Conflict Analysis file failed to open - can't be created");
18718  Utilities->CallLogPop(2210);
18719  return(false);
18720  }
18721  if(LocServiceTimesVector.empty())
18722  {
18723  ShowMessage("No timetabled services found");
18724  TTFile3.close();
18725  DeleteFile(TTFileName3);
18726  Utilities->CallLogPop(2211);
18727  return(false);
18728  }
18729  TTFile3 << "Timetable analysis for timetable: '" + TimetableTitle + "' in conjunction with railway: '" + RailwayTitle + "'\n";
18730  TTFile3 << "See user manual or on-screen help section 5.12 for detailed information.\n\n\n";
18731  SequenceLog += "6\n";
18732 
18733 /*
18734 //print out TrainDataVectorCopy & TrainDataVector for debugging purposes, TrainDataVectorCopy first
18735 
18736 // Double crosslink (shuttle) table:
18737 //Command Format OtherHead NonRepeating- LinkTrain- NonRepeating- Decsription
18738 // Code ShuttleLink- EntryPtr ShuttleLink-
18739 // HeadCode EntryPtr
18740 
18741 //Snt-sh SNTShuttle Y (rtn shuttle) N Y (rtn sh) N Simple shuttle - no feeder service
18742 //Frh-sh TimeCmdHeadCode Y (outwd shuttle) N Y (outwd sh) N Simple shuttle - no finishing service
18743 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18744 //Sns-sh SNSShuttle Y (rtn shuttle) Y (feeder) Y (rtn) Y (fdr) Shuttle link from feeder service
18745 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18746 //Fns-sh FSHNewService Y (outwd shuttle) Y (finishing) Y (outwd sh) Y (finish) Shuttle link to finishing service
18747 //
18748 //Note: Any shuttle start can have any finish - feeder and finish, neither, feeder but no finish & vice versa.
18749 
18750 //NOTE: from above for F-nshs & Sns-fsh in the TrainDataVectorCopy the OtherLinkedHeadCode will be correct as it is derived from LinkTrainEntryPtr which is correct
18751 //but for the original TrainDataVector the OtherLinkedHeadCode will be incorrect, hence have to use the NonRepeatingShuttleLinkHeadCode as that is the correct one
18752 //these were errors when first coded but work ok, just keep in mind when making any changes
18753 
18754 std::ofstream TDVCFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVectorCopy.txt").c_str());
18755 AnsiString OHC = "", NRHC = "";
18756 AnsiString OLk = "", NRLk = "";
18757 TDVCFile << "Note that for the TrainDataVectorCopy OH and OLk should always be the same, as OH is derived from OHLk; and similarly for NR and NRLk\n\n";
18758 for(TTrainDataVector::iterator TDVCIt = TrainDataVectorCopy.begin(); TDVCIt != TrainDataVectorCopy.end(); TDVCIt++)
18759 {
18760  TDVCFile << TDVCIt->ServiceReference + '\n';
18761  TDVCFile << TDVCIt->Description + '\n';
18762  for(unsigned int x = 0; x < TDVCIt->ActionVector.size(); x++)
18763  {
18764  TActionVectorEntry AVE = TDVCIt->ActionVector.at(x);
18765  if(AVE.OtherHeadCode == "")
18766  {
18767  OHC = "OH 0";
18768  }
18769  else
18770  {
18771  OHC = "OH " + AVE.OtherHeadCode;
18772  }
18773  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
18774  {
18775  NRHC = "NR 0";
18776  }
18777  else
18778  {
18779  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
18780  }
18781  if(TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
18782  {
18783  OLk = "OLk 0";
18784  }
18785  else
18786  {
18787  OLk = "OLk " + TDVCIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
18788  }
18789  if(TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
18790  {
18791  NRLk = "NRLk 0";
18792  }
18793  else
18794  {
18795  NRLk = "NRLk " + TDVCIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
18796  }
18797 
18798  if(AVE.FormatType == TimeCmd) //cdt only
18799  {
18800  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
18801  }
18802  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
18803  {
18804  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18805  }
18806  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
18807  {
18808  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18809  }
18810  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
18811  {
18812  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18813  }
18814  else if(AVE.FormatType == StartNew)
18815  {
18816  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-1, AVE.RearStartOrRepeatMins).ElementID
18817  << " FrontStartID " << Track->TrackElementAt(-2, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18818  }
18819  else if(AVE.FormatType == SNTShuttle)
18820  {
18821  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-3, AVE.RearStartOrRepeatMins).ElementID
18822  << " FrontStartID " << Track->TrackElementAt(-4, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18823  }
18824  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
18825  {
18826  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
18827  }
18828  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
18829  {
18830  TDVCFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
18831  }
18832  else if(AVE.FormatType == TimeTimeLoc)
18833  {
18834  TDVCFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
18835  }
18836  else if(AVE.FormatType == PassTime)
18837  {
18838  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
18839  }
18840  else if(AVE.FormatType == ExitRailway)
18841  {
18842  TDVCFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
18843  }
18844  else if(AVE.FormatType == FinRemHere)
18845  {
18846  TDVCFile << "Frh" << '\n';
18847  }
18848  }
18849  TDVCFile << '\n';
18850 }
18851 TDVCFile.close();
18852 
18853 //print out original TrainDataVector for comparison
18854 std::ofstream TDVFile((CurDir + "\\Formatted timetables\\Conflict Analysis " + TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss") + "; " + RailwayTitle + "; " + TimetableTitle + " TrainDataVector.txt").c_str());
18855 TDVFile << "Note that in the TrainDataVector, non-repeating shuttle link services F-nshs and Sns-fsh use the non-repeating headcode (NR) values for the corresponding "
18856  "shuttle headcodes when it should be the other headcode (OH), and the other headcode is unused. The link values are the right way round. Also OH & NR "
18857  "values ARE headcodes and not service references, but OLk and NRLk values are service references.\n\n";
18858 //F-nshs FNSNonRepeatToShuttle N (shld be Y for outwd shuttle) Y (shld be N) Y (correct) N (correct) Feeder service link to shuttle
18859 //Sns-fsh SNSNonRepeatFromShuttle N (shld be Y for rtn shuttle) Y (shld be N) Y (correct) N (correct) Finishing service link from shuttle
18860 for(TTrainDataVector::iterator TDVIt = TrainDataVector.begin(); TDVIt != TrainDataVector.end(); TDVIt++)
18861 {
18862  TDVFile << TDVIt->ServiceReference + '\n';
18863  TDVFile << TDVIt->Description + '\n';
18864  for(unsigned int x = 0; x < TDVIt->ActionVector.size(); x++)
18865  {
18866  TActionVectorEntry AVE = TDVIt->ActionVector.at(x);
18867  if(AVE.OtherHeadCode == "")
18868  {
18869  OHC = "OH 0";
18870  }
18871  else
18872  {
18873  OHC = "OH " + AVE.OtherHeadCode;
18874  }
18875  if(AVE.NonRepeatingShuttleLinkHeadCode == "")
18876  {
18877  NRHC = "NR 0";
18878  }
18879  else
18880  {
18881  NRHC = "NR " + AVE.NonRepeatingShuttleLinkHeadCode;
18882  }
18883  if(TDVIt->ActionVector.at(x).LinkedTrainEntryPtr == 0)
18884  {
18885  OLk = "OLk 0";
18886  }
18887  else
18888  {
18889  OLk = "OLk " + TDVIt->ActionVector.at(x).LinkedTrainEntryPtr->ServiceReference;
18890  }
18891  if(TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr == 0)
18892  {
18893  NRLk = "NRLk 0";
18894  }
18895  else
18896  {
18897  NRLk = "NRLk " + TDVIt->ActionVector.at(x).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
18898  }
18899 
18900  if(AVE.FormatType == TimeCmd) //cdt only
18901  {
18902  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << '\n';
18903  }
18904  if((AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle))
18905  {
18906  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18907  }
18908  else if((AVE.FormatType == FSHNewService) || (AVE.FormatType == SNSShuttle)) //these should have 2 linked services
18909  {
18910  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18911  }
18912  else if(AVE.FormatType == SNSNonRepeatFromShuttle)
18913  {
18914  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << OHC << ' ' << NRHC << ' ' << OLk << ' ' << NRLk << '\n';
18915  }
18916  else if(AVE.FormatType == StartNew)
18917  {
18918  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt RearStartID " << Track->TrackElementAt(-5, AVE.RearStartOrRepeatMins).ElementID
18919  << " FrontStartID " << Track->TrackElementAt(-6, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18920  }
18921  else if(AVE.FormatType == SNTShuttle)
18922  {
18923  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Snt-sh RearStartID " << Track->TrackElementAt(-7, AVE.RearStartOrRepeatMins).ElementID
18924  << " FrontStartID " << Track->TrackElementAt(-8, AVE.FrontStartOrRepeatDigits).ElementID << '\n';
18925  }
18926  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
18927  {
18928  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
18929  }
18930  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
18931  {
18932  TDVFile << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
18933  }
18934  else if(AVE.FormatType == TimeTimeLoc)
18935  {
18936  TDVFile << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
18937  }
18938  else if(AVE.FormatType == PassTime)
18939  {
18940  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
18941  }
18942  else if(AVE.FormatType == ExitRailway)
18943  {
18944  TDVFile << Utilities->Format96HHMM(AVE.EventTime) << " Fer" << '\n';
18945  }
18946  else if(AVE.FormatType == FinRemHere)
18947  {
18948  TDVFile << "Frh" << '\n';
18949  }
18950  }
18951  TDVFile << '\n';
18952 }
18953 TDVFile.close();
18954 //end of debugging
18955 */
18956  //arrivals
18957  if(ArrChecked)
18958  {
18959  //sort in ArrTime order for each location
18960  Ptr1 = LocServiceTimesVector.begin();
18961  Ptr2 = Ptr1 + 1;
18962  while(Ptr2 != LocServiceTimesVector.end())
18963  {
18964  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
18965  {
18966  Ptr2++;
18967  if(Ptr2 == LocServiceTimesVector.end())
18968  {
18969  break;
18970  }
18971  }
18972  std::sort(Ptr1, Ptr2, &LocServiceTimesArrTimeSort);
18973  Ptr1 = Ptr2; //first entry with next name
18974  if(Ptr2 != LocServiceTimesVector.end())
18975  {
18976  Ptr2++;
18977  }
18978  }
18979 
18980  //routine for arrivals - number of trains arriving within the specified range with services listed at the end
18981 
18982  TTFile3 << "Arrival & pass analysis: an asterisk means that the number of same approach code arrivals and passes is equal to or greater than the number of platforms.\n";
18983  TTFile3 << "If the total number of arrivals and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
18984  MinuteString = " minutes";
18985  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
18986  if(ArrRange == 1)
18987  {
18988  MinuteString = " minute";
18989  }
18990  TTFile3 << "Location,Number of,Number of,Services arriving within " << AnsiString(ArrRange) << MinuteString << " with their arrival times and approach codes\n";
18991  TTFile3 << ",Platforms,Trains\n\n";
18992 
18993  Ptr1 = LocServiceTimesVector.begin();
18994  Ptr2 = Ptr1 + 1;
18995  while(Ptr2 != LocServiceTimesVector.end())
18996  {
18997  PreviousService = "";
18998  NumTrainsAtLoc = 0;
18999  ServiceAndRepeatNumTotal = "";
19000  NumPlats = 0;
19001  NumPlatsAtThisLocCalculated = false;
19002  BasicTime = "";
19003  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19004  {
19005  PreviousService = "";
19006  NumTrainsAtLoc = 0;
19007  ServiceAndRepeatNumTotal = "";
19008  NumPlats = 0;
19009  NumPlatsAtThisLocCalculated = false;
19010  BasicTime = "";
19011  Ptr1++;
19012  Ptr2++;
19013  if(Ptr2 == LocServiceTimesVector.end())
19014  {
19015  break;
19016  }
19017  }
19018  if(Ptr2 == LocServiceTimesVector.end())
19019  {
19020  break;
19021  }
19022  while(Ptr2->Location == Ptr1->Location)
19023  {
19024  PreviousService = "";
19025  NumTrainsAtLoc = 0;
19026  ServiceAndRepeatNumTotal = "";
19027  BasicTime = Ptr1->ArrTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19028  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19029  {
19030  break;
19031  }
19032  while(!WithinTimeRange(0, BasicTime, Ptr2->ArrTime, ArrRange) || ((Ptr1->ArrTime == "") && (Ptr2->ArrTime == "")))
19033  {
19034  BasicTime = Ptr2->ArrTime; //used to compare later times or last can exceed first
19035  Ptr1++;
19036  Ptr2++;
19037  if(Ptr2 == LocServiceTimesVector.end())
19038  {
19039  break;
19040  }
19041  if(Ptr2->Location != Ptr1->Location)
19042  {
19043  break;
19044  }
19045  }
19046  if(Ptr2 == LocServiceTimesVector.end())
19047  {
19048  break;
19049  }
19050  if(Ptr2->Location != Ptr1->Location)
19051  {
19052  break;
19053  }
19054  while(WithinTimeRange(1, BasicTime, Ptr2->ArrTime, ArrRange))
19055  {
19056  if((Ptr1->ArrTime == "") && (Ptr2->ArrTime == ""))
19057  {
19058  break;
19059  }
19060  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19061  {
19062  NumPlats = Track->NumberOfPlatforms(0, Ptr1->Location);
19063  NumPlatsAtThisLocCalculated = true;
19064  }
19065  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19066  {
19067  if(ServiceAndRepeatNumTotal == "")
19068  {
19069  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19070  NumTrainsAtLoc = 1;
19071  }
19072  else
19073  {
19074  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->ArrTime;
19075  }
19076  }
19077  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19078  if(ServiceAndRepeatNumTotal == "")
19079  {
19080  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19081  NumTrainsAtLoc = 1;
19082  }
19083  else
19084  {
19085  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->ArrTime;
19086  }
19087  Ptr1 = Ptr2;
19088  Ptr2++;
19089  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(2, BasicTime, Ptr2->ArrTime, ArrRange)))
19090  {
19091  int MaxNumberOfSameDirections = 0;
19092  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, true, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19093  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19094  {
19095 // ShowMessage("Error in arrival analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19096  TTFile3.close();
19097  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19098 // Utilities->CallLogPop(2224);
19099 // return false;
19100  }
19101  AnsiString Asterisk = "";
19102  if(MaxNumberOfSameDirections >= NumPlats)
19103  {
19104  Asterisk = "* ";
19105  }
19106  //print out a single line for number of trains at loc with all service refs
19107  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19108  ArrivalsPrinted = true;
19109  ServiceAndRepeatNumTotal = "";
19110  }
19111  if(Ptr2 == LocServiceTimesVector.end())
19112  {
19113  break;
19114  }
19115  if(Ptr2->Location != Ptr1->Location)
19116  {
19117  break;
19118  }
19119  }
19120  if(Ptr2 == LocServiceTimesVector.end())
19121  {
19122  break;
19123  }
19124  }
19125  }
19126  if(!ArrivalsPrinted)
19127  {
19128  TTFile3 << "Nothing to report for arrivals";
19129  }
19130  TTFile3 << "\n\n";
19131  }
19132  //end of routine for arrivals
19133  SequenceLog += "7\n";
19134  //departures
19135  if(DepChecked)
19136  {
19137  //sort in DepTime order for each location
19138  Ptr1 = LocServiceTimesVector.begin();
19139  Ptr2 = Ptr1 + 1;
19140  while(Ptr2 != LocServiceTimesVector.end())
19141  {
19142  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19143  {
19144  Ptr2++;
19145  if(Ptr2 == LocServiceTimesVector.end())
19146  {
19147  break;
19148  }
19149  }
19150  std::sort(Ptr1, Ptr2, &LocServiceTimesDepTimeSort);
19151  Ptr1 = Ptr2; //first entry with next name
19152  if(Ptr2 != LocServiceTimesVector.end())
19153  {
19154  Ptr2++;
19155  }
19156  }
19157 
19158  //routine for departures - number of trains departing within the specified range with services listed at the end
19159  TTFile3 << "Departure & pass analysis: an asterisk means that the number of same exit code departures and passes is equal to or greater than the number of platforms.\n";
19160  TTFile3 << "If the total number of departures and passes at the same time exceeds the number of platforms the 'Trains present at location analysis' will show an asterisk.\n\n";
19161  MinuteString = " minutes";
19162  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19163  if(DepRange == 1)
19164  {
19165  MinuteString = " minute";
19166  }
19167  TTFile3 << "Location,Number of,Number of,Services departing within " << AnsiString(DepRange) << MinuteString << " with their departure times and exit codes\n";
19168  TTFile3 << ",Platforms,Trains\n\n";
19169 
19170  Ptr1 = LocServiceTimesVector.begin();
19171  Ptr2 = Ptr1 + 1;
19172  while(Ptr2 != LocServiceTimesVector.end())
19173  {
19174  PreviousService = "";
19175  NumTrainsAtLoc = 0;
19176  ServiceAndRepeatNumTotal = "";
19177  NumPlats = 0;
19178  NumPlatsAtThisLocCalculated = false;
19179  BasicTime = "";
19180  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19181  {
19182  PreviousService = "";
19183  NumTrainsAtLoc = 0;
19184  ServiceAndRepeatNumTotal = "";
19185  NumPlats = 0;
19186  NumPlatsAtThisLocCalculated = false;
19187  BasicTime = "";
19188  Ptr1++;
19189  Ptr2++;
19190  if(Ptr2 == LocServiceTimesVector.end())
19191  {
19192  break;
19193  }
19194  }
19195  if(Ptr2 == LocServiceTimesVector.end())
19196  {
19197  break;
19198  }
19199  while(Ptr2->Location == Ptr1->Location)
19200  {
19201  PreviousService = "";
19202  NumTrainsAtLoc = 0;
19203  ServiceAndRepeatNumTotal = "";
19204  BasicTime = Ptr1->DepTime; //used to compare later times - later pointer contents have same or later times as sorted in time order
19205  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19206  {
19207  break;
19208  }
19209  while(!WithinTimeRange(3, BasicTime, Ptr2->DepTime, DepRange) || ((Ptr1->DepTime == "") && (Ptr2->DepTime == "")))
19210  {
19211  BasicTime = Ptr2->DepTime; //used to compare later times or last can exceed first
19212  Ptr1++;
19213  Ptr2++;
19214  if(Ptr2 == LocServiceTimesVector.end())
19215  {
19216  break;
19217  }
19218  if(Ptr2->Location != Ptr1->Location)
19219  {
19220  break;
19221  }
19222  }
19223  if(Ptr2 == LocServiceTimesVector.end())
19224  {
19225  break;
19226  }
19227  if(Ptr2->Location != Ptr1->Location)
19228  {
19229  break;
19230  }
19231  while(WithinTimeRange(4, BasicTime, Ptr2->DepTime, DepRange))
19232  {
19233  if((Ptr1->DepTime == "") && (Ptr2->DepTime == ""))
19234  {
19235  break;
19236  }
19237  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19238  {
19239  NumPlats = Track->NumberOfPlatforms(1, Ptr1->Location);
19240  NumPlatsAtThisLocCalculated = true;
19241  }
19242  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19243  {
19244  if(ServiceAndRepeatNumTotal == "")
19245  {
19246  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
19247  NumTrainsAtLoc = 1;
19248  }
19249  else
19250  {
19251  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum + "," + Ptr1->DepTime;
19252  }
19253  }
19254  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ
19255  if(ServiceAndRepeatNumTotal == "")
19256  {
19257  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
19258  NumTrainsAtLoc = 1;
19259  }
19260  else
19261  {
19262  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum + "," + Ptr2->DepTime;
19263  }
19264  Ptr1 = Ptr2;
19265  Ptr2++;
19266  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (!WithinTimeRange(5, BasicTime, Ptr2->DepTime, DepRange)))
19267  {
19268  int MaxNumberOfSameDirections = 0;
19269  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTArrDep(3, ServiceAndRepeatNumTotal, NumTrainsAtLoc, Ptr1->Location, false, AnalysisError, MaxNumberOfSameDirections); //sort into alphabetical order and remove duplicates
19270  if(AnalysisError) //has to be Ptr1->Location as Ptr2 loc may have changed
19271  {
19272 // ShowMessage("Error in departure analysis - file will be incomplete and/or corrupt. Please send railway and timetable files to railwayfeedback@gmail.com for investigation - thanks. Details: " + ServiceAndRepeatNumTotalOutput);
19273  TTFile3.close();
19274  throw Exception(ServiceAndRepeatNumTotalOutput.c_str());
19275 // Utilities->CallLogPop(2225);
19276 // return false;
19277  }
19278  AnsiString Asterisk = "";
19279  if(MaxNumberOfSameDirections >= NumPlats)
19280  {
19281  Asterisk = "* ";
19282  }
19283  //print out a single line for number of trains at loc with all service refs
19284  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << ServiceAndRepeatNumTotalOutput << '\n'; //no description as >1 service
19285  DeparturesPrinted = true;
19286  ServiceAndRepeatNumTotal = "";
19287  }
19288  if(Ptr2 == LocServiceTimesVector.end())
19289  {
19290  break;
19291  }
19292  if(Ptr2->Location != Ptr1->Location)
19293  {
19294  break;
19295  }
19296  }
19297  if(Ptr2 == LocServiceTimesVector.end())
19298  {
19299  break;
19300  }
19301  }
19302  }
19303  if(!DeparturesPrinted)
19304  {
19305  TTFile3 << "Nothing to report for departures";
19306  }
19307  TTFile3 << "\n\n";
19308  }
19309  //end of routine for departures
19310  SequenceLog += "8\n";
19311 
19312  //list trains at locations at same time
19313 
19314  if(AtLocChecked)
19315  {
19316  //sort in AtLocTime order for each location
19317  Ptr1 = LocServiceTimesVector.begin();
19318  Ptr2 = Ptr1 + 1;
19319  while(Ptr2 != LocServiceTimesVector.end())
19320  {
19321  while(Ptr2->Location == Ptr1->Location) //ends with Ptr2 one past same Location value as Ptr1
19322  {
19323  Ptr2++;
19324  if(Ptr2 == LocServiceTimesVector.end())
19325  {
19326  break;
19327  }
19328  }
19329  std::sort(Ptr1, Ptr2, &LocServiceTimesAtLocTimeSort);
19330  Ptr1 = Ptr2; //first entry with next name
19331  if(Ptr2 != LocServiceTimesVector.end())
19332  {
19333  Ptr2++;
19334  }
19335  }
19336 
19337  //print out simultaneous AtLocs (don't need range of times for AtLocs)
19338  TTFile3 << "Trains present at location analysis: an asterisk means that the number of trains at the location is greater than the number of platforms.\n\n";
19339  TTFile3 << "Location,Number of,Number of,Time,Services at the location at that time\n";
19340  TTFile3 << ",Platforms,Trains,\n\n";
19341  AnsiString ServiceAndRepeatNumTotal = "", ServiceAndRepeatNumTotalOutput = "";
19342  Ptr1 = LocServiceTimesVector.begin();
19343  Ptr2 = Ptr1 + 1;
19344  while(Ptr2 != LocServiceTimesVector.end())
19345  {
19346  PreviousService = "";
19347  ServiceAndRepeatNumTotal = "";
19348  NumTrainsAtLoc = 0;
19349  NumPlats = 0;
19350  NumPlatsAtThisLocCalculated = false;
19351  FrhCount = 0;
19352 
19353  while((Ptr2->Location != Ptr1->Location) || ((Ptr1->Location == "") && (Ptr2->Location == "")))
19354  {
19355  PreviousService = "";
19356  ServiceAndRepeatNumTotal = "";
19357  NumTrainsAtLoc = 0;
19358  NumPlats = 0;
19359  NumPlatsAtThisLocCalculated = false;
19360  FrhCount = 0;
19361  Ptr1++;
19362  Ptr2++;
19363  if(Ptr2 == LocServiceTimesVector.end())
19364  {
19365  break;
19366  }
19367  }
19368  if(Ptr2 == LocServiceTimesVector.end())
19369  {
19370  break;
19371  }
19372  while(Ptr2->Location == Ptr1->Location)
19373  {
19374  if(Ptr1->FrhMarker == "Frh") //this test is made here and each time Ptr1 increases with Ptr1 & 2 at same loc so as to catch them all
19375  {
19376  FrhCount++;
19377  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19378  }
19379  PreviousService = "";
19380  NumTrainsAtLoc = 0;
19381  ServiceAndRepeatNumTotal = "";
19382  if((Ptr1->Location == "") && (Ptr2->Location == ""))
19383  {
19384  break;
19385  }
19386  while((Ptr2->AtLocTime != Ptr1->AtLocTime) || ((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == "")))
19387  {
19388  Ptr1++;
19389  if(Ptr1->FrhMarker == "Frh")
19390  {
19391  FrhCount++;
19392  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19393  }
19394  Ptr2++;
19395  if(Ptr2 == LocServiceTimesVector.end())
19396  {
19397  break;
19398  }
19399  if(Ptr2->Location != Ptr1->Location)
19400  {
19401  break;
19402  }
19403  }
19404  if(Ptr2 == LocServiceTimesVector.end())
19405  {
19406  break;
19407  }
19408  if(Ptr2->Location != Ptr1->Location)
19409  {
19410  break;
19411  }
19412  while(Ptr2->AtLocTime == Ptr1->AtLocTime)
19413  {
19414  if((Ptr1->AtLocTime == "") && (Ptr2->AtLocTime == ""))
19415  {
19416  break;
19417  }
19418  if(!NumPlatsAtThisLocCalculated) //num plats at relevant location, reset when locations change
19419  {
19420  NumPlats = Track->NumberOfPlatforms(2, Ptr1->Location);
19421  NumPlatsAtThisLocCalculated = true;
19422  }
19423  if(Ptr1->ServiceAndRepeatNum != PreviousService) //don't print it twice if same as last - as will be if >1 service at same loc at same time
19424  {
19425  if(ServiceAndRepeatNumTotal == "")
19426  {
19427  ServiceAndRepeatNumTotal = Ptr1->ServiceAndRepeatNum;
19428  NumTrainsAtLoc = 1;
19429  }
19430  else
19431  {
19432  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr1->ServiceAndRepeatNum;
19433  }
19434  }
19435  PreviousService = Ptr2->ServiceAndRepeatNum; //last service at relevant time, reset when times differ, has to be Ptr2 to compare Ptr1 at next round when incremented
19436  if(ServiceAndRepeatNumTotal == "")
19437  {
19438  ServiceAndRepeatNumTotal = Ptr2->ServiceAndRepeatNum;
19439  NumTrainsAtLoc = 1;
19440  }
19441  else
19442  {
19443  ServiceAndRepeatNumTotal = ServiceAndRepeatNumTotal + "," + Ptr2->ServiceAndRepeatNum;
19444  }
19445  Ptr1 = Ptr2;
19446  if(Ptr1->FrhMarker == "Frh")
19447  {
19448  FrhCount++;
19449  Ptr1->FrhMarker = "FrhCounted"; //to avoid double counting
19450  }
19451  Ptr2++;
19452  if((Ptr2 == LocServiceTimesVector.end()) || (Ptr2->Location != Ptr1->Location) || (Ptr2->AtLocTime != Ptr1->AtLocTime))
19453  {
19454 //old text //only print out if no remainers (1st condition), change in remainers (2nd condition) or change in ServiceAndRepeatNumTotalOutput, and >1 train (later condition)
19455 //new text //don't print out if all remainers or if only 1 train at loc
19456  ServiceAndRepeatNumTotalOutput = ConsolidateSARNTAtLoc(1, ServiceAndRepeatNumTotal, NumTrainsAtLoc); //sort into alphabetical order, remove duplicates, and calculate new value for NumTrainsAtLoc
19457 //old condits if((FrhCount == 0) || (FrhCount != LastFrhCount) || (PreviousServiceAndRepeatNumTotalOutput != ServiceAndRepeatNumTotalOutput))//don't print if same output
19458 /*new condits*/ if((NumTrainsAtLoc > 1) && ((FrhCount < NumTrainsAtLoc) || (FrhCount != LastFrhCount)))
19459  {
19460  AnsiString Asterisk = "";
19461  if(NumTrainsAtLoc > NumPlats)
19462  {
19463  Asterisk = "* ";
19464  }
19465  //print out a single line for number of trains at loc with all service refs
19466  if(FrhCount == 0)
19467  {
19468  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << "," << ServiceAndRepeatNumTotalOutput << '\n';
19469  }
19470  else if(FrhCount == 1)
19471  {
19472  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (1 remains here)," << ServiceAndRepeatNumTotalOutput << '\n';
19473  }
19474  else
19475  {
19476  TTFile3 << Asterisk << Ptr1->Location << "," << NumPlats << "," << NumTrainsAtLoc << "," << Ptr1->AtLocTime << " (" << FrhCount << " remain here)," << ServiceAndRepeatNumTotalOutput << '\n';
19477  }
19478  LastFrhCount = FrhCount;
19479  PreviousServiceAndRepeatNumTotalOutput = ServiceAndRepeatNumTotalOutput;
19480  AtLocsPrinted = true;
19481  ServiceAndRepeatNumTotal = "";
19482  }
19483  }
19484  if(Ptr2 == LocServiceTimesVector.end())
19485  {
19486  break;
19487  }
19488  if(Ptr2->Location != Ptr1->Location)
19489  {
19490  break;
19491  }
19492  }
19493  if(Ptr2 == LocServiceTimesVector.end())
19494  {
19495  break;
19496  }
19497  }
19498  }
19499  if(!AtLocsPrinted)
19500  {
19501  TTFile3 << "Nothing to report for trains at locations";
19502  }
19503  TTFile3 << "\n\n";
19504  //end of simultaneous AtLocs
19505  }
19506  SequenceLog += "9\n";
19507 
19508 /*
19509 //start of debugging section
19510  //print out the full vector here for testing purposes
19511  TTFile3 << "Full LocServiceTimesVector\n\n";
19512  TTFile3 << "Location,AtLocTime,ArrTime,DepTime,ServiceAndRepeatNum,Description\n\n";
19513 
19514  for(TLocServiceTimesVector::iterator Ptr = LocServiceTimesVector.begin(); Ptr != LocServiceTimesVector.end(); Ptr++)
19515  {
19516  TTFile3 << Ptr->Location << "," << Ptr->AtLocTime << "," << Ptr->ArrTime << "," << Ptr->DepTime << "," << Ptr->ServiceAndRepeatNum << "," << Ptr->FrhMarker << '\n';
19517  }
19518 
19519  TTFile3 << "\n\n\n";
19520 //end of debugging
19521 */
19522 
19523 /*cdt analysis - added at v2.10.0
19524 2 pass system: 1st extract as a single service all Snt (or Snt-sh) starts, with Fns/Sns links combined (and F-nshs/Sns-sh) (though add a new
19525 changeover code [chr XXXX - 'change ref + new reference] until come to Fjo, Frh, Frh-sh, Fer (ignore exit loc as can't stop there), ignore jbos &
19526 repeats, but with fsp & rsp store all the foregoing service entries along with the split reference & add that to the relevant Sfs entry as a new
19527 service. For shuttles with feeder start with feeder & progress into shuttle, ending when finish & remain here or progressing into the finishing
19528 service.
19529 
19530 Use The TrainDataVectorCopy as that has all unique service refs.
19531 
19532 2nd run the cdt checker similar to that in SecondPassActions, but where a same name found either side of a changeover code quote both refs. Add a
19533 similar unexpected cdt check where if have different locs either side of a cdt then may be inappropriate.
19534 
19535 First create a new TrainDataVector from earlier copy as above with single services
19536 */
19537  if(DirChecked)
19538  {
19539  //direction analysis added at v2.10.0
19540  TTrainDataEntry SingleServiceEntry, PartServiceEntry, NewPartServiceEntry, TempEntry;
19541  TTrainDataVector SingleServiceVector, PartServiceVector;
19542 
19543  //find new train services (Snt or Snt-sh) & remember that entries can be in any order
19544  //NB: ALWAYS use OtherHeadCode (which is now a service reference) for any follow-on service
19545  TTFile3 << "Train direction analysis - consisting of train facing directions on creation and possible missing or questionable changes of direction:\n\n";
19546  for(unsigned int x = 0; x < TrainDataVectorCopy.size(); x++)
19547  {
19549  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
19550  {
19551  TDE.ActionVector.erase(TDE.ActionVector.end() - 1); //strip repeat entry if present
19552  }
19553  const TActionVector &AV = TDE.ActionVector;
19554  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
19555  {
19556  SingleServiceEntry = TDE;
19557  TActionVector &SSAV = SingleServiceEntry.ActionVector;
19558  for(unsigned int y = 0; y < SSAV.size(); y++)
19559  {
19560  if((SSAV.at(y).Command == "Fjo") || (SSAV.at(y).Command == "Frh") || (SSAV.at(y).Command == "Fer") || (SSAV.at(y).Command == "Frh-sh"))
19561  {
19562  SingleServiceVector.push_back(SingleServiceEntry); //push the complete entry
19563  break; //finished with this one
19564  }
19565  else if((SSAV.at(y).Command == "fsp") || (SSAV.at(y).Command == "rsp"))
19566  {
19567  PartServiceEntry = TDE; //start with complete entry
19568  PartServiceEntry.ActionVector.clear(); //clear AV
19569  for(unsigned int z = 0; z <= y; z++)
19570  {
19571  PartServiceEntry.ActionVector.push_back(TDE.ActionVector.at(z)); //add back all AVs up to & inc fsp/rsp
19572  if(z == y)
19573  {
19574  PartServiceEntry.ActionVector.at(z).Command = "chr-sp"; //change split command to chr
19575  PartServiceEntry.ActionVector.at(z).OtherHeadCode = PartServiceEntry.ActionVector.at(z).LinkedTrainEntryPtr->ServiceReference;
19576  }
19577  }
19578  PartServiceVector.push_back(PartServiceEntry);
19579  if(SSAV.at(y).Command == "fsp")
19580  {
19581  SSAV.at(y).Command = "Front split - original service continues below";
19582  SSAV.at(y).OtherHeadCode = "";
19583  }
19584  if(SSAV.at(y).Command == "rsp")
19585  {
19586  SSAV.at(y).Command = "Rear split - original service continues below";
19587  SSAV.at(y).OtherHeadCode = "";
19588  }
19589  //don't break & continue here because the original train carries on
19590  }
19591  else if(SSAV.at(y).Command == "Fns")
19592  {
19593  SSAV.at(y).Command = "chr-Fns";
19594  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
19595  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
19596  break; //from y loop
19597  }
19598  else if(SSAV.at(y).Command == "Fns-sh")
19599  {
19600  SSAV.at(y).Command = "chr-Fns-sh";
19601  SSAV.at(y).OtherHeadCode = SSAV.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19602  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
19603  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
19604  break; //from y loop
19605  }
19606  else if(SSAV.at(y).Command == "F-nshs")
19607  {
19608  SSAV.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
19609  SSAV.at(y).OtherHeadCode = SSAV.at(y).LinkedTrainEntryPtr->ServiceReference;
19610  SSAV.at(y).NonRepeatingShuttleLinkHeadCode = "";
19611  PartServiceVector.push_back(SingleServiceEntry); //not complete yet
19612  break; //from y loop
19613  }
19614  }
19615  }
19616  }
19617  SequenceLog += "10\n";
19618  //now have all complete entries in SingleServiceVector and all part services in PartServiceVector but without any follow-ons
19619 
19620  //Now add Sns & Sns-sh services to PartServiceVector entries
19621  AnsiString NextRef;
19622  while(!PartServiceVector.empty())
19623  {
19624  PartServiceEntry = PartServiceVector.at(0); //deal with front entry and add new entries at the back
19625  for(unsigned int y = 0; y < PartServiceEntry.ActionVector.size(); y++)
19626  {
19627  if(PartServiceEntry.ActionVector.at(y).Command.SubString(1,3) == "chr")
19628  {
19629  NextRef = PartServiceEntry.ActionVector.at(y).OtherHeadCode;
19630  }
19631  }
19632  //find it in TrainDataVectorCopy
19633  bool FinishType = true, FoundFlag = false;
19634  while(FinishType)
19635  {
19636  TempEntry = GetServiceFromVector(0, NextRef, TrainDataVectorCopy, FinishType, FoundFlag); //FinishType is a bool where false = Final (Fjo, Frh, Fer, or
19637  //Frh-sh); true = MoreToCome (Fns, Fns-sh, F-nshs)
19638  if(FoundFlag)
19639  {
19640  for(unsigned int y = 1; y < TempEntry.ActionVector.size(); y++) //starts at 1 as that is the entry after the start entry
19641  {
19642  if((TempEntry.ActionVector.at(y).Command == "") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
19643  {
19644  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19645  }
19646  else if((TempEntry.ActionVector.at(y).Command[1] != 'F') && (TempEntry.ActionVector.at(y).Command != "fsp") && (TempEntry.ActionVector.at(y).Command != "rsp") && (TempEntry.ActionVector.at(y).FormatType != Repeat))
19647  {
19648  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19649  }
19650  else
19651  {
19652  if((TempEntry.ActionVector.at(y).Command == "Fjo") || (TempEntry.ActionVector.at(y).Command == "Frh") || (TempEntry.ActionVector.at(y).Command == "Fer") || (TempEntry.ActionVector.at(y).Command == "Frh-sh"))
19653  {
19654  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19655  SingleServiceVector.push_back(PartServiceVector.at(0)); //push the complete entry
19656  PartServiceVector.erase(PartServiceVector.begin());
19657  break; //from y loop
19658  }
19659  else if((TempEntry.ActionVector.at(y).Command == "fsp") || (TempEntry.ActionVector.at(y).Command == "rsp"))
19660  {
19661  NewPartServiceEntry = PartServiceVector.at(0); //covers everything up to but excluding the split
19662  NewPartServiceEntry.ActionVector.push_back(TempEntry.ActionVector.at(y)); //now includes the split
19663  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).Command = "chr-sp"; //change split command to chr
19664  NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).OtherHeadCode = NewPartServiceEntry.ActionVector.at(NewPartServiceEntry.ActionVector.size() - 1).LinkedTrainEntryPtr->ServiceReference;
19665  PartServiceVector.push_back(NewPartServiceEntry); //new entry for the split service
19666  if(TempEntry.ActionVector.at(y).Command == "fsp")
19667  {
19668  TempEntry.ActionVector.at(y).Command = "Front split - original service continues below";
19669  TempEntry.ActionVector.at(y).OtherHeadCode = "";
19670  }
19671  if(TempEntry.ActionVector.at(y).Command == "rsp")
19672  {
19673  TempEntry.ActionVector.at(y).Command = "Rear split - original service continues below";
19674  TempEntry.ActionVector.at(y).OtherHeadCode = "";
19675  }
19676  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y));
19677  }
19678  else if(TempEntry.ActionVector.at(y).Command == "Fns")
19679  {
19680  TempEntry.ActionVector.at(y).Command = "chr-Fns";
19681  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
19682  TempEntry.ActionVector.at(y).OtherHeadCode = NextRef;
19683  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
19684  break; //from y loop
19685  }
19686  else if(TempEntry.ActionVector.at(y).Command == "Fns-sh")
19687  {
19688  TempEntry.ActionVector.at(y).Command = "chr-Fns-sh";
19689  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19690  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
19691  NextRef = TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkEntryPtr->ServiceReference;
19692  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
19693  break; //from y loop
19694  }
19695  else if(TempEntry.ActionVector.at(y).Command == "F-nshs")
19696  {
19697  TempEntry.ActionVector.at(y).Command = "chr-F-nshs"; //NonRepeatingShuttleLinkHeadCode is the shuttle headcode
19698  TempEntry.ActionVector.at(y).OtherHeadCode = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
19699  TempEntry.ActionVector.at(y).NonRepeatingShuttleLinkHeadCode = "";
19700  NextRef = TempEntry.ActionVector.at(y).LinkedTrainEntryPtr->ServiceReference;
19701  PartServiceVector.at(0).ActionVector.push_back(TempEntry.ActionVector.at(y)); //not complete yet
19702  break; //from y loop
19703  }
19704  }
19705  }
19706  }
19707  else
19708  {
19709  SequenceLog += + "11\n";
19710  throw Exception("Unable to find service reference " + NextRef + " Last ref checked = " + TempEntry.ServiceReference);
19711  }
19712  }
19713  }
19714  if(!PartServiceVector.empty())
19715  {
19716  SequenceLog += "12\n";
19717  throw Exception("PartServiceVector should be empty here - size = " + AnsiString(PartServiceVector.size()));
19718  }
19719  SequenceLog += "13\n";
19720  /*
19721  form:-
19722  HeadCode[;Description (plain text, no commas or semicolons)][;StartSpeed(kph); MaxRunningSpeed(kph); Mass(tonnes, prog converts to kg);
19723  MaxBrakeRate(tonnes force, prog converts to m/s/s); & gross power(kW, prog converts to power at rail in w)
19724  then multiple entries, separated by commas, of the form:-
19725 
19726  HH:MM;Snt;RearStartIdent FrontStartIdent }StartNew }
19727  HH:MM;Snt-sh;RearStartIdent FrontStartIdent;Fsh HeadCode }SNTShuttle }
19728  HH:MM;Sns-sh;Fxx-sh HeadCode;F-nshs HeadCode (non-repeating)}SNSShuttle }
19729 
19730  HH:MM;Command;HeadCode (Sfs Sns jbo fsp rsp Fns Fjo Frh-sh) }TimeCmdHeadCode } Train action entries
19731  HH:MM;F-nshs;NonRepeatingShuttleLinkHeadCode }FNSNonRepeatToShuttle }
19732  HH:MM;Sns-fsh;NonRepeatingShuttleLinkHeadCode }SNSNonRepeatFromShuttle }
19733 
19734  HH:MM;Command (cdt) }TimeCmd }
19735  HH:MM;Command;new description (dsc) }TimeCmdDescription }
19736  HH:MM;Location (arr & dep) }TimeLoc }
19737  HH:MM;HH:MM;Location }TimeTimeLoc }
19738  HH:MM;pas;Location }PassTime }
19739  HH:MM;Fns-sh;Snx-sh HeadCode;Sns-fsh HeadCode (non-rep) }FSHNewService }
19740  HH:MM;Fer;set of allowable IDs }ExitRailway }
19741  Command (Frh only) }FinRemHere }
19742 
19743  R;mm;dd;nn. Repeat Repeat entry
19744 
19745  Formats:
19746 
19747  Command only: Frh
19748  Time;Command: cdt
19749  Time;Command;new description: dsc
19750  Time;Command;Headcode: Sfs Sns jbo fsp rsp Fns Fjo Frh-sh F-nshs Sns-fsh
19751  Time;Command;2 Element IDs: Snt
19752  Time;Comand;n Element IDs: Fer
19753  Time;Command;rep Headcode;nonrep Headcode: Sns-sh Fns-sh
19754  Time;Command;2 Element IDs;Headcode Snt-sh
19755  Time;Command;Location pas
19756  Time;Location Arr Dep
19757  Time;Time;Location Arr & dep together
19758  */
19759 
19760 /*
19761 Perform the starting direction check (Snt & Snt-sh entries). Starts with the train's front element,
19762 checking forwards until it comes to a continuation (no report), a location name that is not null and
19763 different to the train's front element name (whether null or not) (no report), a leading point
19764 (no report) or buffers (report).
19765 */
19766  bool BufferFacingUnReportedFlag = true;
19767  bool TrainFacingBuffersReported = false; //new flag added at v2.19.0 for 'no facing buffers' message instead of BufferFacingUnReportedFlag
19768  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
19769  {
19770  TTrackElement ThisElement, NextElement;
19771  TTrainDataEntry TDE = SingleServiceVector.at(x);
19772  if(TDE.ActionVector.at(TDE.ActionVector.size() - 1).FormatType == Repeat)
19773  {
19774  SequenceLog += "13a\n";
19775  throw Exception("Repeat entry present in SingleServiceVector at position " + AnsiString(x));
19776  }
19777  const TActionVector &AV = TDE.ActionVector;
19778  if((AV.at(0).Command == "Snt") || (AV.at(0).Command == "Snt-sh"))
19779  {
19780  bool BufferFlag = false;
19781  int FrontTVPos = AV.at(0).FrontStartOrRepeatDigits;
19782  int RearTVPos = AV.at(0).RearStartOrRepeatMins;
19783  AnsiString FrontLocName = AV.at(0).LocationName;
19784  int NextEntryPos, NextExitPos;
19785  ThisElement = Track->TrackElementAt(1395, FrontTVPos);
19786  int ThisExitPos;
19787  if(ThisElement.Conn[0] == RearTVPos)
19788  {
19789  ThisExitPos = 1;
19790  }
19791  else if(ThisElement.Conn[1] == RearTVPos)
19792  {
19793  ThisExitPos = 0;
19794  }
19795  else if(ThisElement.Conn[2] == RearTVPos)
19796  {
19797  ThisExitPos = 3;
19798  }
19799  else if(ThisElement.Conn[3] == RearTVPos)
19800  {
19801  ThisExitPos = 2;
19802  }
19803  if((ThisElement.TrackType == Buffers) && (ThisExitPos == 0))//pos 0 is the buffer
19804  {
19805  BufferFlag = true;
19806  }
19807  else //continue tracking forwards
19808  {
19809  while(true)
19810  {
19811  if(ThisElement.Conn[ThisExitPos] == -1)
19812  {
19813  SequenceLog = "13b\n";
19814  throw Exception("ThisElement connects to -1 for " + TDE.ServiceReference);
19815  }
19816  NextElement = Track->TrackElementAt(1396, ThisElement.Conn[ThisExitPos]);
19817  NextEntryPos = ThisElement.ConnLinkPos[ThisExitPos];
19818  if((NextElement.TrackType == Points) && ((NextEntryPos == 0) || (NextEntryPos == 2))) //leading points
19819  {
19820  BufferFlag = false; //should already be false
19821  break;
19822  }
19823  else if(NextElement.TrackType == Continuation)
19824  {
19825  BufferFlag = false;
19826  break;
19827  }
19828  else if((NextElement.ActiveTrackElementName != "") && (NextElement.ActiveTrackElementName != FrontLocName))
19829  {
19830  BufferFlag = false;
19831  break;
19832  }
19833  else if(NextElement.TrackType == Buffers)
19834  {
19835  BufferFlag = true;
19836  break;
19837  }
19838  else if((NextElement.TrackType == Points) && ((NextEntryPos == 1) || (NextEntryPos == 3))) //trailing points
19839  {
19840  ThisElement = NextElement;
19841  ThisExitPos = 0;
19842  continue;
19843  }
19844  else
19845  {
19846  if(NextEntryPos == 0)
19847  {
19848  NextExitPos = 1;
19849  }
19850  else if(NextEntryPos == 1)
19851  {
19852  NextExitPos = 0;
19853  }
19854  else if(NextEntryPos == 2)
19855  {
19856  NextExitPos = 3;
19857  }
19858  else if(NextEntryPos == 3)
19859  {
19860  NextExitPos = 2;
19861  }
19862  }
19863  ThisElement = NextElement;
19864  ThisExitPos = NextExitPos;
19865  }
19866  }
19867  if(BufferFlag)
19868  {
19869  if(BufferFacingUnReportedFlag)
19870  {
19871  TTFile3 << "Train facing direction on creation analysis:-\n\n";
19872  BufferFacingUnReportedFlag = false;
19873  }
19874  if(AV.at(1).Command != "cdt") //added at v2.19.0
19875  {
19876  TTFile3 << "Service " + TDE.ServiceReference + " facing buffers on creation with no immediate change of direction\n";
19877  TrainFacingBuffersReported = true; //added at v2.19.0
19878  }
19879  }
19880  }
19881  }
19882  if(!TrainFacingBuffersReported) //added at v2.19.0
19883  {
19884  TTFile3 << "Nothing to report for train facing directions\n\n";
19885  }
19886  else
19887  {
19888  TTFile3 << '\n';
19889  }
19890  SequenceLog += "13c\n";
19891 
19892  //Perform the missing cdt check. Check every entry similar to the check in SecondPassActions and if find any print out the full sequence and service entries
19893  AnsiString LocationNameToBeChecked = "";
19894  bool MissingcdtUnreportedFlag = true;
19895  TNumList MarkerList;
19896  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
19897  {
19898  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
19899  unsigned int y = 0;
19900  int FirstInstance = 9999, SecondInstance = 9999; //9999 ensures won't be marked if not changed
19901  bool FullBreak = false;
19902  MarkerList.clear();
19903  // first discard unlocated Snt entries as they don't have location name set
19904  while((y < TDEntry.ActionVector.size()) && !FullBreak)
19905  // need to check each location name separately in turn, skipped for SignallerControl entries
19906  {
19907  if(/*(TDEntry.ActionVector.at(y).Command == "Fer") || */(TDEntry.ActionVector.at(y).FormatType == Repeat) ||
19908  (TDEntry.ActionVector.at(y).Command == "Fjo") || (TDEntry.ActionVector.at(y).Command == "Frh") ||
19909  (TDEntry.ActionVector.at(y).Command == "Frh-sh"))
19910  {
19911  break; // out of the 'while' loop since have reached the end
19912  }
19913  LocationNameToBeChecked = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
19914  if((TDEntry.ActionVector.at(y).Command == "Snt") && (TDEntry.ActionVector.at(y).LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
19915  {
19916  int EntryPos = TDEntry.ActionVector.at(0).RearStartOrRepeatMins; //this is a track vector position
19917  LocationNameToBeChecked = Track->TrackElementAt(1678, EntryPos).ActiveTrackElementName;
19918  }
19919  if(LocationNameToBeChecked == "")
19920  {
19921  if(y == 0) //unlocated and un-named Snt, so skip this value of y
19922  {
19923  y++;
19924  continue;
19925  }
19926  LocationNameToBeChecked = TDEntry.ActionVector.at(y).LocationName; //the only un-named values for ActionVectorEntry::LocationName
19927  } //are for unlocated Snts and Fers
19928  FirstInstance = y;
19929  for(unsigned int z = y; z < TDEntry.ActionVector.size(); z++)
19930  {
19931  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
19932  if(/*(AVEntry.Command == "Fer") || */(AVEntry.FormatType == Repeat) ||
19933  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
19934  (AVEntry.Command == "Frh-sh"))
19935  {
19936  break; // out of the 'z' loop since have reached the end & 'Fer' & 'Repeat' have no location name set
19937  }
19938  if(AVEntry.Command == "cdt")
19939  {
19940  break; // out of the 'z' loop since the check is only valid up to a change of direction
19941  }
19942  if(AVEntry.LocationName == LocationNameToBeChecked)
19943  {
19944  continue; // keep going while name same
19945  }
19946  if(AVEntry.LocationName != LocationNameToBeChecked)
19947  // if name different check forwards to see if repeats
19948  {
19949  for(unsigned int a = z; a < TDEntry.ActionVector.size(); a++)
19950  {
19951  AnsiString LocationName;
19952  if(TDEntry.ActionVector.at(a).Command == "cdt")
19953  {
19954  break; // out of the 'a' & 'z' loops since the check is only valid up to a change of direction
19955  }
19956  if(TDEntry.ActionVector.at(a).Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
19957  {
19958  int ExitLoc = TDEntry.ActionVector.at(a).ExitList.front();
19959  LocationName = Track->TrackElementAt(1679, ExitLoc).ActiveTrackElementName;
19960  }
19961  else
19962  {
19963  LocationName = TDEntry.ActionVector.at(a).LocationName;
19964  }
19965  if(LocationName == LocationNameToBeChecked)
19966  {
19967  SecondInstance = a;
19968  AnsiString Sequence = TDEntry.ServiceReference;
19969  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
19970  {
19971  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
19972  {
19973  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
19974  }
19975  }
19976  if(MissingcdtUnreportedFlag)
19977  {
19978  TTFile3 << "Possibly missing changes of direction - these will be missing unless the service travels in a loop back to the locations marked:-\n\n";
19979  }
19980  TTFile3 << LocationNameToBeChecked << " is listed twice with no direction change between in service sequence: " << Sequence << "\n\n";
19981  MarkerList.push_back(FirstInstance);
19982  MarkerList.push_back(SecondInstance);
19983  SingleServiceOutput(0, x, MarkerList, SingleServiceVector, TTFile3);
19984  MissingcdtUnreportedFlag = false;
19985  FullBreak = true; //no more checks for this sequence
19986  break; //out of the a & z loops
19987  }
19988  }
19989  break; // out of the 'z' loop since have checked 'a' as far as need to
19990  }
19991  }
19992  y++;
19993  }
19994  }
19995  if(MissingcdtUnreportedFlag)
19996  {
19997  TTFile3 << "Nothing to report for missing changes of direction\n\n";
19998  }
19999  else
20000  {
20001  TTFile3 << '\n';
20002  }
20003  SequenceLog += "14\n";
20004 
20005 /* Perform the questionable cdt check. Examine each service in the SingleServiceVector, and if don't find the same
20006  name either side of a cdt (before the next cdt) then flag as a questionable cdt.
20007  Method: Have an outer loop for each service that looks for cdts. When found work backwards to the last cdt or beginning and std::list all the
20008  locations excluding the cdt location. Then work forwards to the next cdt or the end and do the same. Sort each list and make unique (duplicated
20009  names on one side of a cdt already checked either by the tt validator or the missing cdt check). Then compare the two lists and if any location
20010  included in both then ok, else report as questionable. If one list is empty then it is reported.
20011 */
20012  typedef std::list<AnsiString> TLocList;
20013  TLocList BackwardList, ForwardList;
20014  bool IntroLineNeeded = true;
20015  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20016  {
20017  unsigned int cdtPosition = 9999;
20018  AnsiString cdtLocation = "";
20019  bool FoundSameName = false;
20020  bool FerEntry = false;
20021  MarkerList.clear();
20022  const TTrainDataEntry &TDEntry = SingleServiceVector.at(x);
20023  for(unsigned int y = 0; y <= TDEntry.ActionVector.size(); y++) // <= because need to examine Fer endings after reached end of vector
20024  // need to check each location name separately in turn, skipped for SignallerControl entries
20025  {
20026  if((y == TDEntry.ActionVector.size()) && !FerEntry)
20027  {
20028  break;
20029  }
20030  BackwardList.clear();
20031  ForwardList.clear();
20032  bool ValidEnd = false;
20033  if(y < TDEntry.ActionVector.size())
20034  {
20035  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20036  if((AVEntry.FormatType == Repeat) || //end of SSVector, shouldn't be any repeats
20037  (AVEntry.Command == "Fjo") || (AVEntry.Command == "Frh") ||
20038  (AVEntry.Command == "Frh-sh"))
20039  {
20040  ValidEnd = true;
20041  }
20042  }
20043  if(FerEntry || ValidEnd)
20044  {
20045  if(MarkerList.empty())
20046  {
20047  break; // out of the 'y' loop since have reached the end & nothing to report
20048  }
20049  else
20050  {
20051  AnsiString Sequence = TDEntry.ServiceReference;
20052  for(unsigned int b = 0; b < TDEntry.ActionVector.size(); b++)
20053  {
20054  if(TDEntry.ActionVector.at(b).Command.SubString(1,3) == "chr")
20055  {
20056  Sequence = Sequence + AnsiString(" -> ") + TDEntry.ActionVector.at(b).OtherHeadCode;
20057  }
20058  }
20059  MarkerList.sort();
20060  if(IntroLineNeeded)
20061  {
20062  TTFile3 << "Questionable change of direction analysis.\n\n";
20063  TTFile3 << "For marked changes of direction there are no same-name locations listed both above (up to the start or another direction change)\n";
20064  TTFile3 << "and below (down to the end or another direction change) but not counting the change of direction location itself.\n\n";
20065  TTFile3 << "These changes of direction are probably valid for movements to and from depots but all should be checked to\n";
20066  TTFile3 << "make sure that none has been included incorrectly:\n\n";
20067  IntroLineNeeded = false;
20068  }
20069  TTFile3 << "Service sequence " << Sequence << " contains questionable changes of direction:-\n\n";
20070  SingleServiceOutput(1, x, MarkerList, SingleServiceVector, TTFile3);
20071  break;
20072  }
20073  }
20074  if(y < TDEntry.ActionVector.size()) //if it is == ...size() then shouldn't have reached here
20075  {
20076  const TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(y);
20077  if(AVEntry.Command != "cdt")
20078  {
20079  continue; //only looking for cdts
20080  }
20081  //here have found a cdt
20082  cdtPosition = y;
20083  cdtLocation = AVEntry.LocationName;
20084  for(int z = y - 1; z >= 0; z--)
20085  {
20086  const TActionVectorEntry &AVEntry2 = TDEntry.ActionVector.at(z);
20087  if(AVEntry2.Command == "cdt")
20088  {
20089  break; //don't look further back than the last cdt
20090  }
20091  AnsiString LocName = ""; //this section where continuation name assigned for unlocated Snts added at v2.18.0
20092  if((AVEntry2.Command == "Snt") && (AVEntry2.LocationType == EnRoute)) //unlocated Snt - check if the continuation has a name
20093  {
20094  int EntryPos = AVEntry2.RearStartOrRepeatMins; //this is a track vector position
20096  }
20097  if(LocName == "")
20098  {
20099  LocName = AVEntry2.LocationName; //the only un-named values for ActionVectorEntry::LocationName
20100  } //are for unlocated Snts and Fers
20101  if((LocName != "") && (AVEntry2.LocationName != cdtLocation)) //if an earlier entry == cdtLocation will have been picked up in an earlier check
20102  {
20103  BackwardList.push_back(LocName);
20104  }
20105  }
20106  BackwardList.sort();
20107  BackwardList.unique();
20108  for(unsigned int z = y + 1; z < TDEntry.ActionVector.size(); z++)
20109  {
20110  const TActionVectorEntry &AVEntry3 = TDEntry.ActionVector.at(z);
20111  if(/*(AVEntry3.Command == "Fer") || */(AVEntry3.FormatType == Repeat) ||
20112  (AVEntry3.Command == "Fjo") || (AVEntry3.Command == "Frh") ||
20113  (AVEntry3.Command == "Frh-sh") || (AVEntry3.Command == "cdt"))
20114  {
20115  break; // out of the 'z' loop since have reached another cdt or the end
20116  }
20117  AnsiString LocName = "";
20118  if(AVEntry3.Command == "Fer") //this section where continuation name assigned for Fers added at v2.18.0
20119  {
20120  int ExitLoc = AVEntry3.ExitList.front();
20121  LocName = Track->TrackElementAt(1681, ExitLoc).ActiveTrackElementName;
20122  FerEntry = true;
20123  }
20124  else
20125  {
20126  LocName = AVEntry3.LocationName;
20127  }
20128  if((LocName != "") && (AVEntry3.LocationName != cdtLocation)) //if a later entry == cdtLocation will have been picked up in an earlier check
20129  {
20130  ForwardList.push_back(LocName);
20131  }
20132  }
20133  ForwardList.sort();
20134  ForwardList.unique();
20135  FoundSameName = false;
20136  //now have both lists compiled (may be empty) so check for same name in both & report if don't find any
20137  if(!BackwardList.empty() && !ForwardList.empty())
20138  {
20139  for(TLocList::iterator BLIt = BackwardList.begin(); BLIt != BackwardList.end(); BLIt++)
20140  {
20141  for(TLocList::iterator FLIt = ForwardList.begin(); FLIt != ForwardList.end(); FLIt++)
20142  {
20143  if(*BLIt == *FLIt)
20144  {
20145  FoundSameName = true;
20146  }
20147  }
20148  }
20149  }
20150  if(!FoundSameName) //report the inability to find same name
20151  {
20152  MarkerList.push_back(cdtPosition);
20153  }
20154  }
20155  }
20156  }
20157  if(IntroLineNeeded)
20158  {
20159  TTFile3 << "Nothing to report for questionable changes of direction\n\n";
20160  }
20161  else
20162  {
20163  TTFile3 << '\n';
20164  }
20165 /*
20166 //debug section
20167 //print all SSVector for diagnostic purposes
20168  TTFile3 << "Whole SSVector\n\n";
20169  TNumList EmptyList;
20170  for(unsigned int x = 0; x < SingleServiceVector.size(); x++)
20171  {
20172  SingleServiceOutput(, x, EmptyList, SingleServiceVector, TTFile3);
20173  }
20174 //end of debug section
20175 */
20176  }
20177  SequenceLog += "15\n";
20178  TTFile3.close();
20179  Utilities->CallLogPop(2212);
20180  return(true);
20181  }
20182 
20183  catch(const Exception &e) //non error catch
20184  {
20185  AnsiString TTErrorFileName = "Analysis Error.txt";
20186  TTErrorFileName = CurDir + "\\Formatted timetables\\" + TTErrorFileName;
20187  std::ofstream TTError(TTErrorFileName.c_str());
20188  if(TTError == 0)
20189  {
20190  ShowMessage("Analysis error file failed to open - can't be created");
20191  Utilities->CallLogPop(2233);
20192  return(false);
20193  }
20194  AnsiString TimeNow = TDateTime::CurrentDateTime().FormatString("dd-mm-yyyy hh.nn.ss");
20195  TTError << TimeNow.c_str() << '\n' << ArrRange << '\n' << ArrChecked << '\n' << DepRange << '\n' <<
20196  DepChecked << '\n' << AtLocChecked << '\n' << SequenceLog << '\n' << AnsiString(e.Message);
20197 
20198  TTError.close();
20199  ShowMessage("Error in Conflict Analysis: A file called 'Analysis Error.txt' has been created in your Formatted timetables folder. Please send this file together with your railway and timetable files to railwayfeedback@gmail.com for investigation - many thanks");
20200  Utilities->CallLogPop(2226);
20201  return(false);
20202  }
20203 }
20204 
20205 // ---------------------------------------------------------------------------
20206 void TTrainController::SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
20207 {
20208  Utilities->CallLog.push_back(Utilities->TimeStamp() + AnsiString(SSVectorNumber) + ',' + ",SingleServiceOutput");
20209  if((SSVectorNumber < 0) || ((unsigned int)SSVectorNumber >= SingleServiceVector.size()))
20210  {
20211  throw Exception("SSVectorNumber out of range, = " + AnsiString(SSVectorNumber) + ", Vector size = " + SingleServiceVector.size());
20212  }
20213  TTrainDataEntry SingleService = SingleServiceVector.at(SSVectorNumber);
20214  {
20215  VecFile << ",Initial service reference " << SingleService.ServiceReference + '\n';
20216  AnsiString Marker = "";
20217  for(unsigned int x = 0; x < SingleService.ActionVector.size(); x++)
20218  {
20219  Marker = ',';
20220  for(TNumListIterator MLIt = MarkerList.begin(); MLIt != MarkerList.end(); MLIt++)
20221  {
20222  if(int(x) == *MLIt)
20223  {
20224  Marker = "-->,";
20225  break;
20226  }
20227  }
20228  TActionVectorEntry AVE = SingleService.ActionVector.at(x);
20229  if(AVE.FormatType == StartNew)
20230  {
20231  AnsiString RearID = Track->TrackElementAt(1397, AVE.RearStartOrRepeatMins).ElementID;
20232  AnsiString FrontID = Track->TrackElementAt(1398, AVE.FrontStartOrRepeatDigits).ElementID;
20233  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << '\n';
20234  }
20235  if(AVE.FormatType == SNTShuttle)
20236  {
20237  AnsiString RearID = Track->TrackElementAt(1399, AVE.RearStartOrRepeatMins).ElementID;
20238  AnsiString FrontID = Track->TrackElementAt(1400, AVE.FrontStartOrRepeatDigits).ElementID;
20239  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << RearID << ' ' << FrontID << ' ' << AVE.OtherHeadCode << '\n';
20240  }
20241  if(AVE.FormatType == SNSShuttle) //should all have been converted to chr
20242  {
20243  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << ' ' << AVE.NonRepeatingShuttleLinkHeadCode << '\n';
20244  }
20245  if(AVE.FormatType == Repeat) //shouldn't be any repeats, only here to show if any have been copied
20246  {
20247  VecFile << Marker << "Repeat " << AVE.RearStartOrRepeatMins << ' ' << AVE.FrontStartOrRepeatDigits << ' ' << AVE.NumberOfRepeats << '\n';
20248  }
20249  if((AVE.FormatType == TimeCmd) || (AVE.FormatType == TimeCmdHeadCode) || (AVE.FormatType == FNSNonRepeatToShuttle) || (AVE.FormatType == FSHNewService))
20250  {
20251  TActionVectorEntry AVHolder = AVE;
20252  if(AVE.Command.SubString(1,3) == "chr")
20253  {
20254  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "sp")
20255  {
20256  AVE.Command = "Change of service to " + AVE.OtherHeadCode + " after split";
20257  AVE.OtherHeadCode = "";
20258  }
20259  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns")
20260  {
20261  AVE.Command = "Change of service to ";
20262  }
20263  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "Fns-sh")
20264  {
20265  AVE.Command = "Change to shuttle finishing service";
20266  }
20267  if(AVE.Command.SubString(5, AVE.Command.Length() - 4) == "F-nshs")
20268  {
20269  AVE.Command = "Change to shuttle service " + AVE.OtherHeadCode + " from feeder";
20270  AVE.OtherHeadCode = "";
20271  }
20272  }
20273  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << AVE.Command << ' ' << AVE.OtherHeadCode << '\n';
20274  AVE = AVHolder;
20275  }
20276  else if((AVE.FormatType == TimeLoc) && (AVE.ArrivalTime != TDateTime(-1)))
20277  {
20278  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << " Arr " << AVE.LocationName << '\n';
20279  }
20280  else if((AVE.FormatType == TimeLoc) && (AVE.DepartureTime != TDateTime(-1)))
20281  {
20282  VecFile << Marker << Utilities->Format96HHMM(AVE.DepartureTime) << " Dep " << AVE.LocationName << '\n';
20283  }
20284  else if(AVE.FormatType == TimeTimeLoc)
20285  {
20286  VecFile << Marker << Utilities->Format96HHMM(AVE.ArrivalTime) << ' ' << Utilities->Format96HHMM(AVE.DepartureTime) << ' ' << AVE.LocationName << '\n';
20287  }
20288  else if(AVE.FormatType == PassTime)
20289  {
20290  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << ' ' << "Pass" << ' ' << AVE.LocationName << '\n';
20291  }
20292  else if(AVE.FormatType == ExitRailway) //ListOfExits added at v2.10.0
20293  {
20294  AnsiString ListOfExits = "";
20295  for(TNumListIterator NLIt = AVE.ExitList.begin(); NLIt != AVE.ExitList.end(); NLIt++)
20296  {
20297  ListOfExits += AnsiString(Track->TrackElementAt(1432, *NLIt).ElementID) + ' ';
20298  }
20299  VecFile << Marker << Utilities->Format96HHMM(AVE.EventTime) << " Fer " << ListOfExits <<'\n';
20300  }
20301  else if(AVE.FormatType == FinRemHere)
20302  {
20303  VecFile << Marker << "Frh" << '\n';
20304  }
20305  }
20306  VecFile << '\n';
20307  }
20308  Utilities->CallLogPop(2318);
20309 }
20310 
20311 // ---------------------------------------------------------------------------
20312 
20313 TTrainDataEntry TTrainController::GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
20314 {
20315  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetServiceFromVector," + HeadCode);
20316  FoundFlag = false;
20317  FinishType = true;
20318  for(unsigned int x = 0; x < Vector.size(); x++)
20319  {
20320 // AnsiString ThisRef = Vector.at(x).ServiceReference;
20321  if(Vector.at(x).ServiceReference == HeadCode)
20322  {
20323  if(Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1).FormatType == Repeat) //shouldn't be any repeats
20324  {
20325  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 2);
20326  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
20327  {
20328  FinishType = false;
20329  }
20330  }
20331  else
20332  {
20333  TActionVectorEntry AVE = Vector.at(x).ActionVector.at(Vector.at(x).ActionVector.size() - 1);
20334  if((AVE.Command == "Fjo") || (AVE.Command == "Frh") || (AVE.Command == "Fer") || (AVE.Command == "Frh-sh"))
20335  {
20336  FinishType = false;
20337  }
20338  }
20339  FoundFlag = true;
20340  Utilities->CallLogPop(2319);
20341  return(Vector.at(x));
20342  }
20343  }
20344  Utilities->CallLogPop(2320);
20345  return(Vector.at(Vector.size() - 1)); //return last for want of returning something
20346 }
20347 
20348 // ---------------------------------------------------------------------------
20349 
20350 bool TTrainController::WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange) //times are "HH:MM"
20351 {
20352 //convert times to integer minutes
20353  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",WithinTimeRange," + Time1 + "," + Time2 + "," + AnsiString(MinuteRange));
20354  if((Time1 == "") || (Time2 == ""))
20355  {
20356  Utilities->CallLogPop(2213);
20357  return(false);
20358  }
20359  int Mins = Time1.SubString(4,2).ToInt();
20360  int Hours = Time1.SubString(1,2).ToInt();
20361  int Time1Mins = (Hours * 60) + Mins;
20362  Mins = Time2.SubString(4,2).ToInt();
20363  Hours = Time2.SubString(1,2).ToInt();
20364  int Time2Mins = (Hours * 60) + Mins;
20365  if(abs(Time1Mins - Time2Mins) <= MinuteRange)
20366  {
20367  Utilities->CallLogPop(2214);
20368  return(true);
20369  }
20370  Utilities->CallLogPop(2215);
20371  return(false);
20372 }
20373 
20374 // ---------------------------------------------------------------------------
20375 
20376 AnsiString TTrainController::ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival,
20377  bool &AnalysisError, int &MaxNumberOfSameDirections)
20378 {
20379  //input consists of services and service Arr or Dep times as a comma separated list, Location needed to determine direction information
20380 
20381  try
20382  {
20383  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTArrDep," + Input);
20384  AnsiString Output = "", OneService = "", TempStr1 = "", TempStr2 = "";
20385  int SCPos = 0;
20386  std::list<AnsiString> ServiceList; //this is the list of services with times extracted from Input - not to be confused with ServiceCallingPointsList
20387  //first change every second comma in Input to a semicolon so can separate services but keep times with services
20388  bool EvenComma = false;
20389  for(int x = 1; x <= Input.Length(); x++)
20390  {
20391  TempStr1 = Input[x];
20392  if(TempStr1 == AnsiString(',') && EvenComma)
20393  {
20394  TempStr2 += ';';
20395  }
20396  else
20397  {
20398  TempStr2 += Input[x];
20399  }
20400  if(TempStr1 == AnsiString(','))
20401  {
20402  EvenComma = !EvenComma;
20403  }
20404  }
20405  //load up the list of services with associated times
20406  while(TempStr2.Length() > 0)
20407  {
20408  SCPos = TempStr2.Pos(';');
20409  if(SCPos > 0) //0 if not found, as won't be when only one service left
20410  {
20411  OneService = TempStr2.SubString(1, SCPos - 1);
20412  ServiceList.push_back(OneService);
20413  TempStr2 = TempStr2.SubString(SCPos + 1, TempStr2.Length() - SCPos);
20414  }
20415  else //no semicolon so looking at last (or only) element
20416  {
20417  ServiceList.push_back(TempStr2);
20418  TempStr2 = "";
20419  }
20420  }
20421  ServiceList.sort(); // alphabetical order
20422  ServiceList.unique(); //remove duplicates
20423  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
20424 
20425  //now add direction information from AllServiceCallingLocsMap - key is service ref and value a list of calling points in order
20426  int DirectionMarker = 0; //this is added in & runs from 1 upwards, same marker for diff services = same direction
20427  //first add the direction marker "&0" for not yet allocated - '&' is an identifier
20428  std::list<AnsiString>::iterator SLIt, SLIt1, SLIt2, SLIt3;
20429 
20430  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20431  {
20432  *SLIt = *SLIt + "&0"; //add in a basic direction marker to each service
20433  }
20434  SLIt3 = ServiceList.end();
20435  SLIt3--; //so points to last element
20436  AnsiString ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatInfo1, RepeatInfo2; //1 refers to first for..next loop & 2 to second
20437  int AmpersandPos, SpacePos, CommaPos1, CommaPos2, RepeatNum1, RepeatNum2;
20438  TAllServiceCallingLocsMap::iterator ASCLIt1, ASCLIt2;
20439  TServiceCallingLocsList ServiceCallingLocsList1, ServiceCallingLocsList2;
20440  MaxNumberOfSameDirections = 0; //at end of each SLIt loop if SameDirectionCount > MaxNumberOfSameDirections then MaxNumberOfSameDirections = SameDirectionCount
20441  int SameDirectionCount = 0; //starts at 1 at each SLIt loop (because SLIt1 entry already has a DirectionMarker) and increments for every same direction
20442 
20443  for(std::list<AnsiString>::iterator SLIt1 = ServiceList.begin(); SLIt1 != SLIt3; SLIt1++) //should be end() - 1 but can't use -1 with lists so have to improvise
20444  {
20445  SLIt = SLIt1;
20446  SLIt++; //so points to one after SLIt1
20447  if(SLIt1->SubString(SLIt1->Length() - 1, 2) != AnsiString("&0"))
20448  {
20449  continue; //already allocated so skip to the next
20450  }
20451  else
20452  {
20453  CommaPos1 = SLIt1->Pos(','); //can't be 0
20454  ServiceRef1 = SLIt1->SubString(1, CommaPos1 - 1);
20455  //but this contains "(First service..." etc so need to strip these, but use to extract RepeatNum
20456  SpacePos = ServiceRef1.Pos(' ');
20457  RepeatNum1 = 0;
20458  if(SpacePos > 0) //otherwise it's already correct
20459  {
20460  RepeatInfo1 = ServiceRef1.SubString(SpacePos + 2, ServiceRef1.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
20461  ServiceRef1 = ServiceRef1.SubString(1, SpacePos - 1);
20462  if(RepeatInfo1[1] == 'F')
20463  {
20464  RepeatNum1 = 0;
20465  }
20466  else
20467  {
20468  SpacePos = RepeatInfo1.Pos(' ');
20469  RepeatNum1 = RepeatInfo1.SubString(SpacePos + 1, RepeatInfo1.Length() - SpacePos).ToInt();
20470  }
20471  }
20472  AnsiTime1 = SLIt1->SubString(CommaPos1 + 1, SLIt1->Length() - CommaPos1);
20473  //but this includes the "&0" etc so need to strip these
20474  AmpersandPos = AnsiTime1.Pos('&');
20475  AnsiTime1 = AnsiTime1.SubString(1, AmpersandPos - 1);
20476 
20477  ASCLIt1 = AllServiceCallingLocsMap.find(ServiceRef1);
20478  if(ASCLIt1 == AllServiceCallingLocsMap.end()) //can't find it
20479  {
20480  throw Exception("ASCLIt1 Error in " + Input);
20481  }
20482  ServiceCallingLocsList1 = ASCLIt1->second;
20483  AmpersandPos = SLIt1->Pos('&');
20484  *SLIt1 = SLIt1->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20485  *SLIt1 = *SLIt1 + AnsiString(++DirectionMarker); //now add the next marker (pre-increment), allow for it being more than one digit
20486 
20487  SameDirectionCount = 1;
20488  for(SLIt2 = SLIt; SLIt2 != ServiceList.end(); SLIt2++)
20489  {
20490  CommaPos2 = SLIt2->Pos(','); //can't be 0
20491  ServiceRef2 = SLIt2->SubString(1, CommaPos2 - 1);
20492  //but this contains "(First service..." etc so need to strip these
20493  SpacePos = ServiceRef2.Pos(' ');
20494  RepeatNum2 = 0;
20495  if(SpacePos > 0) //otherwise it's already correct
20496  {
20497  RepeatInfo2 = ServiceRef2.SubString(SpacePos + 2, ServiceRef2.Length() - SpacePos - 2); //drops the brackets and leaves "First service", "Repeat 2" etc
20498  ServiceRef2 = ServiceRef2.SubString(1, SpacePos - 1);
20499  if(RepeatInfo2[1] == 'F')
20500  {
20501  RepeatNum2 = 0;
20502  }
20503  else
20504  {
20505  SpacePos = RepeatInfo2.Pos(' ');
20506  RepeatNum2 = RepeatInfo2.SubString(SpacePos + 1, RepeatInfo2.Length() - SpacePos).ToInt();
20507  }
20508  }
20509  AnsiTime2 = SLIt2->SubString(CommaPos2 + 1, SLIt2->Length() - CommaPos2);
20510  //but this includes the "&0" etc so need to strip these
20511  AmpersandPos = AnsiTime2.Pos('&');
20512  AnsiTime2 = AnsiTime2.SubString(1, AmpersandPos - 1);
20513 
20514  ASCLIt2 = AllServiceCallingLocsMap.find(ServiceRef2);
20515  if(ASCLIt2 == AllServiceCallingLocsMap.end()) //can't find it
20516  {
20517  throw Exception("ASCLIt2 Error in " + Input);
20518  }
20519  ServiceCallingLocsList2 = ASCLIt2->second;
20520  //now compare the two
20521  if(SameDirection(0, ServiceRef1, ServiceRef2, AnsiTime1, AnsiTime2, RepeatNum1, RepeatNum2, ServiceCallingLocsList1, ServiceCallingLocsList2, Location, Arrival))
20522  {
20523  int AmpersandPos = SLIt2->Pos('&');
20524  *SLIt2 = SLIt2->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20525  *SLIt2 = *SLIt2 + AnsiString(DirectionMarker); //now add the same marker as *SLIt1
20526  SameDirectionCount++;
20527  }
20528  }
20529  if(SameDirectionCount > MaxNumberOfSameDirections)
20530  {
20531  MaxNumberOfSameDirections = SameDirectionCount;
20532  }
20533  }
20534  }
20535 
20536  if(SLIt3->SubString(SLIt3->Length() - 1, 2) == AnsiString("&0")) //*SLTIt3 is the last in the list and may not have been allocated, if not it doesn't match
20537  {
20538  //any existing direction so allocate it now
20539  AmpersandPos = SLIt3->Pos('&');
20540  *SLIt3 = SLIt3->SubString(1, AmpersandPos); //truncate up to & leave in the '&'
20541  *SLIt3 = *SLIt3 + AnsiString(++DirectionMarker);
20542  }
20543  //now change direction markers to upper case letters beginning with 'A' (and continuing with 'AA' if exceed 26) & add a comma before so have ServiceRef, DirectionMarker, Time
20544  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20545  {
20546  //extract the DirectionMarker as an integer
20547  AmpersandPos = SLIt->Pos('&');
20548  AnsiString DirectionMarkerString = SLIt->SubString(AmpersandPos + 1, SLIt->Length() - AmpersandPos); //extract the number as an ansistring
20549  AnsiString ServiceWithoutMarker = SLIt->SubString(1, AmpersandPos - 1); //truncate the &number
20550  DirectionMarker = DirectionMarkerString.ToInt();
20551  AnsiString DirectionSuffix = "";
20552  char c;
20553  if(DirectionMarker < 27)
20554  {
20555  c = 64 + DirectionMarker; //so 1 -> 'A'
20556  DirectionSuffix = "," + AnsiString(c);
20557  }
20558  else if(DirectionMarker < 53)
20559  {
20560  c = 65 + DirectionMarker - 27; //so 27 -> 'AA'
20561  DirectionSuffix = ",A" + AnsiString(c);
20562  }
20563  else
20564  {
20565  DirectionSuffix = ",?"; //shouldn'tn ever get this far!
20566  }
20567  *SLIt = ServiceWithoutMarker + DirectionSuffix;
20568  }
20569  //now prepare the final consolidated output
20570  for(SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20571  {
20572  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
20573  }
20574  if(Output.Length() > 0)
20575  {
20576  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
20577  }
20578  Utilities->CallLogPop(2216);
20579  return(Output);
20580  }
20581 
20582  catch(const Exception &e) //non error catch
20583  {
20584  AnalysisError = true;
20585  Utilities->CallLogPop(2227);
20586  return(e.Message);
20587  }
20588 }
20589 
20590 // ---------------------------------------------------------------------------
20591 
20592 AnsiString TTrainController::ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
20593 {
20594  //similar to above but doesn't include times in the input
20595  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",ConsolidateSARNTAtLoc," + Input);
20596  AnsiString InternalInput = Input, Output = "", OneService = "";
20597  int CommaPos = 0;
20598  std::list<AnsiString> ServiceList;
20599  //load up the list
20600  while(InternalInput.Length() > 0)
20601  {
20602  CommaPos = InternalInput.Pos(',');
20603  if(CommaPos > 0) //0 if not found, as won't be when only one service left
20604  {
20605  OneService = InternalInput.SubString(1, CommaPos - 1);
20606  ServiceList.push_back(OneService);
20607  InternalInput = InternalInput.SubString(CommaPos + 1, InternalInput.Length() - CommaPos);
20608  }
20609  else //no comma so looking at last (or only) element
20610  {
20611  ServiceList.push_back(InternalInput);
20612  InternalInput = "";
20613  }
20614  }
20615 
20616  ServiceList.sort(); // alphabetical order
20617  ServiceList.unique(); //remove duplicates
20618  NumTrainsAtLoc = ServiceList.size(); //calc this after removing duplicates as may not have changed
20619  for(std::list<AnsiString>::iterator SLIt = ServiceList.begin(); SLIt != ServiceList.end(); SLIt++)
20620  {
20621  Output = Output + *SLIt + ","; //will end up with an unwanted comma at the end
20622  }
20623  if(Output.Length() > 0)
20624  {
20625  Output = Output.SubString(1, Output.Length() - 1); //remove the last comma
20626  }
20627  Utilities->CallLogPop(2217);
20628  return(Output);
20629 }
20630 
20631 // ---------------------------------------------------------------------------
20632 
20633 
20634 bool TTrainController::SameDirection(int Caller, AnsiString Ref1In, AnsiString Ref2In, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1,
20635  TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
20636 {
20637  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SameDirection," + Ref1In + "," + Ref2In + "," + Time1 + "," + Time2 + "," +
20638  AnsiString(RepeatNum1) + "," + AnsiString(RepeatNum2) + "," + Location);
20639 
20640  std::list<AnsiString>::iterator LP1, LP2, ListPtr1, ListPtr2, LocPtr1, LocPtr2; //LP1 & 2 are temporary pointers, ListPtrs are
20641  //general list pointers, LocPtrs point to Location in the two lists
20642 
20643  //first find the relevant values for LocPtr1 & LocPtr2 taking account of cdts and times
20644  //for List1
20645  bool LocFound = false;
20646  AnsiString Ref1 = Ref1In, Ref2 = Ref2In;
20647  int IncMinutes;
20648  TDateTime FirstServiceTime;
20649 
20650  //first need to strip off /1, /2 etc if present from Ref1 & Ref2 (leave Ref1In & Ref2In for error message & retain value as target in finding the correct reference for cdts)
20651  int Ref1Target = 0, Ref1Count = 0;
20652  int Ref2Target = 0, Ref2Count = 0;
20653 
20654 /* drop this after retained slashes in ServiceRef
20655  int SlashPos = Ref1.Pos('/');
20656  if(SlashPos > 0) //if 0 Ref1 == Ref1In & target stays at 0
20657  {
20658  Ref1Target = Ref1.SubString(SlashPos + 1, Ref1.Length() - SlashPos).ToInt();
20659  Ref1 = Ref1.SubString(1, SlashPos - 1); //truncate up to but omit '/'
20660  }
20661  int Ref2Target = 0, Ref2Count = 0;
20662  SlashPos = Ref2.Pos('/');
20663  if(SlashPos > 0) //if 0 leave as is
20664  {
20665  Ref2Target = Ref2.SubString(SlashPos + 1, Ref2.Length() - SlashPos).ToInt();
20666  Ref2 = Ref2.SubString(1, SlashPos - 1); //truncate up to but omit '/'
20667  }
20668 */
20669 
20670  for(ListPtr1 = List1.begin(); ListPtr1 != List1.end(); ListPtr1++) //note that when this routine entered Ref1In & Ref2In are already set to the correct services,
20671  {
20672  //even if others have same names. But if there are cdt's then need to refind the correct service
20673  if((*ListPtr1) == Location) //
20674  {
20675  LocPtr1 = ListPtr1; //may be modified later
20676  LocFound = true;
20677  }
20678  if(ListPtr1->SubString(1, 3) == "%%%")
20679  {
20680  AnsiString CDTTime = ListPtr1->SubString(4, 5);
20681  //now adjust the time to correspond to the repeat if there is one
20682  if(RepeatNum1 > 0) //if it is 0 then AnsiTime1 is already valid
20683  {
20684  IncMinutes = -1;
20685  FirstServiceTime = TDateTime(-1);
20686  bool BreakFlag = false;
20687  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
20688  {
20689  if(TDVIt->ServiceReference == Ref1)
20690  {
20691  if(Ref1Target > Ref1Count)
20692  {
20693  Ref1Count++;
20694  continue;
20695  }
20696  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
20697  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
20698  {
20699  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
20700  {
20701  FirstServiceTime = AVIt->EventTime; //i.e. the FirstService value of CDTTime
20702  BreakFlag = true;
20703  break;
20704  }
20705  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime) //add arr & dep in case find sooner (though dep shouldn't be sooner)
20706  {
20707  FirstServiceTime = AVIt->ArrivalTime;
20708  BreakFlag = true;
20709  break;
20710  }
20711  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
20712  {
20713  FirstServiceTime = AVIt->DepartureTime;
20714  BreakFlag = true;
20715  break;
20716  }
20717  }
20718  if(BreakFlag)
20719  {
20720  break;
20721  }
20722  }
20723  }
20724  if(IncMinutes == -1)
20725  {
20726  throw Exception("Failed to find service for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20727  }
20728  if(FirstServiceTime == TDateTime(-1))
20729  {
20730  throw Exception("Failed to find first service time for ServiceRef1 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20731  }
20732  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(60, FirstServiceTime, RepeatNum1, IncMinutes));
20733  }
20734  if(!Arrival && (Time1 == CDTTime)) //continue if equal in case next is a departure for the Location
20735  {
20736  LocFound = false;
20737  continue;
20738  }
20739  if(Arrival && (Time1 == CDTTime)) //gone far enough so can stop
20740  {
20741  break;
20742  }
20743  if(Time1 > CDTTime) //not there yet so go on
20744  {
20745  LocFound = false;
20746  continue;
20747  }
20748  if(Time1 < CDTTime) //gone too far so can stop now
20749  {
20750  break;
20751  }
20752  }
20753  }
20754  if(!LocFound) //have to find it in both lists
20755  {
20756  Utilities->CallLogPop(2228);
20757  return( false);
20758  }
20759  //for List2
20760  LocFound = false;
20761  for(ListPtr2 = List2.begin(); ListPtr2 != List2.end(); ListPtr2++)
20762  {
20763  if((*ListPtr2) == Location)
20764  {
20765  LocPtr2 = ListPtr2; //may be modified later
20766  LocFound = true;
20767  }
20768  if(ListPtr2->SubString(1, 3) == "%%%")
20769  {
20770  AnsiString CDTTime = ListPtr2->SubString(4, 5);
20771  //now adjust the time to correspond to the repeat if there is one
20772  if(RepeatNum2 > 0) //if it is 0 then AnsiTime1 is already valid
20773  {
20774  IncMinutes = -1;
20775  FirstServiceTime = TDateTime(-1);
20776  bool BreakFlag = false;
20777  for(TTrainDataVector::iterator TDVIt = TrainDataVectorCopy.begin(); TDVIt != TrainDataVectorCopy.end(); TDVIt++)
20778  {
20779  if(TDVIt->ServiceReference == Ref2)
20780  {
20781  if(Ref2Target > Ref2Count)
20782  {
20783  Ref2Count++;
20784  continue;
20785  }
20786  IncMinutes = TDVIt->ActionVector.back().RearStartOrRepeatMins;
20787  for(TActionVector::iterator AVIt = TDVIt->ActionVector.begin(); AVIt != TDVIt->ActionVector.end(); AVIt++)
20788  {
20789  if(Utilities->Format96HHMM(AVIt->EventTime) == CDTTime)
20790  {
20791  FirstServiceTime = AVIt->EventTime;
20792  BreakFlag = true;
20793  break;
20794  }
20795  if(Utilities->Format96HHMM(AVIt->ArrivalTime) == CDTTime)
20796  {
20797  FirstServiceTime = AVIt->ArrivalTime;
20798  BreakFlag = true;
20799  break;
20800  }
20801  if(Utilities->Format96HHMM(AVIt->DepartureTime) == CDTTime)
20802  {
20803  FirstServiceTime = AVIt->DepartureTime;
20804  BreakFlag = true;
20805  break;
20806  }
20807  }
20808  if(BreakFlag)
20809  {
20810  break;
20811  }
20812  }
20813  }
20814  if(IncMinutes == -1)
20815  {
20816  throw Exception("IncMinutes -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20817  }
20818  if(FirstServiceTime == TDateTime(-1))
20819  {
20820  throw Exception("First service time -1 for ServiceRef2 in SameDirection " + Ref1In + " " + Ref2In + " " + Time1 + " " + Time2 + " " + AnsiString(RepeatNum1) + " " + AnsiString(RepeatNum2) + " " + Location);
20821  }
20822  CDTTime = Utilities->Format96HHMM(TrainController->GetRepeatTime(61, FirstServiceTime, RepeatNum2, IncMinutes));
20823  }
20824  if(!Arrival && (Time2 == CDTTime)) //continue if equal in case next is a departure for the Location
20825  {
20826  LocFound = false;
20827  continue;
20828  }
20829  if(Arrival && (Time2 == CDTTime)) //gone far enough so can stop
20830  {
20831  break;
20832  }
20833  if(Time2 > CDTTime) //not there yet so go on
20834  {
20835  LocFound = false;
20836  continue;
20837  }
20838  if(Time2 < CDTTime) //gone too far so can stop now
20839  {
20840  break;
20841  }
20842  }
20843  }
20844  if(!LocFound) //have to find it in both lists, and should be found but allow for it not being
20845  {
20846  Utilities->CallLogPop(2229);
20847  return( false);
20848  }
20849  //now, for the arrival analysis, see if there is a common location before the LocPtrs & within any cdts, and if so return true, else return false
20850  //set ListPtr1 to the search start position
20851  if(Arrival)
20852  {
20853  LP1 = List1.begin();
20854  LP1--; //now points to before the first entry
20855  for(ListPtr1 = LocPtr1; ListPtr1 != LP1; ListPtr1--) //search backwards from Location
20856  {
20857  if(ListPtr1 == List1.begin())
20858  {
20859  break;
20860  }
20861  if(ListPtr1->SubString(1, 3) == "%%%") //a cdt event
20862  {
20863  ListPtr1++; //point to one past the cdt
20864  break;
20865  }
20866  }
20867  //set ListPtr2 to the search start position
20868  LP2 = List2.begin();
20869  LP2--; //now points to before the first entry
20870  for(ListPtr2 = LocPtr2; ListPtr2 != LP2; ListPtr2--)
20871  {
20872  if(ListPtr2 == List2.begin())
20873  {
20874  break;
20875  }
20876  if(ListPtr2->SubString(1, 3) == "%%%") //a cdt event
20877  {
20878  ListPtr2++; //point to one past the cdt
20879  break;
20880  }
20881  }
20882  //ListPtr1 & 2 now at search start position
20883  LP1 = ListPtr1;
20884  LP2 = ListPtr2;
20885  //now search forwards, i.e. for common locations before Location
20886  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
20887  {
20888  if(ListPtr1 == LocPtr1) //reached Location without finding a common earlier location so skip to the backwards check
20889  {
20890  break;
20891  }
20892  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location
20893  {
20894  break;
20895  }
20896  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
20897  {
20898  if(ListPtr2 == LocPtr2) //not found common earlier location so go to the next ListPtr1
20899  {
20900  break;
20901  }
20902  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common earlier location so go to the next ListPtr1
20903  {
20904  break;
20905  }
20906  if((*ListPtr1) == (*ListPtr2)) //found a common earlier location
20907  {
20908  Utilities->CallLogPop(2230);
20909  return( true);
20910  }
20911  }
20912  }
20913  }
20914 
20915  //now, for the departure analysis, reset the start positions and search locations after Location
20916 
20917  else
20918  {
20919  LP1 = LocPtr1;
20920  LP1++; //start at one past the location itself
20921  LP2 = LocPtr2;
20922  LP2++;
20923  for(ListPtr1 = LP1; ListPtr1 != List1.end(); ListPtr1++)
20924  {
20925  if(ListPtr1 == List1.end()) //reached end point so stop
20926  {
20927  break;
20928  }
20929  if(ListPtr1->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location
20930  {
20931  break;
20932  }
20933  for(ListPtr2 = LP2; ListPtr2 != List2.end(); ListPtr2++)
20934  {
20935  if(ListPtr2 == List2.end()) //reached end point so go to next ListPtr1
20936  {
20937  break;
20938  }
20939  if(ListPtr2->SubString(1, 3) == "%%%") //reached a cdt event without finding a common location so go to the next ListPtr1
20940  {
20941  break;
20942  }
20943  if((*ListPtr1) == (*ListPtr2)) //found a common later location
20944  {
20945  Utilities->CallLogPop(2231);
20946  return( true);
20947  }
20948  }
20949  }
20950  }
20951  Utilities->CallLogPop(2232);
20952  return( false);
20953 }
20954 
20955 // ---------------------------------------------------------------------------
20956 
20957 AnsiString TTrainController::GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
20958 {
20959  // changed at v2.7.0 to show allowable exit elements
20960  if(ExitList.empty())
20961  {
20962  return("");
20963  }
20964  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",GetExitLocationAndAt");
20965  AnsiString StartName = Track->TrackElementAt(735, *(ExitList.begin())).ActiveTrackElementName;
20966  AnsiString ExitLocList = "";
20967  AllowedExits = "";
20968 
20969  unsigned int Counter = 0;
20970  for(TNumListIterator ELIt = ExitList.begin(); ELIt != ExitList.end(); ELIt++)
20971  {
20972  ExitLocList += Track->TrackElementAt(1018, *ELIt).ElementID + " ";
20973  Counter++;
20974  if(((Counter % 6) == 0) && (Counter < (ExitList.size() - 1))) // only add a newline if more to come
20975  {
20976  ExitLocList += "\n";
20977  }
20978  }
20979  if(StartName == "")
20980  {
20981  if(ExitList.size() == 1)
20982  {
20983  AnsiString ID = Track->TrackElementAt(738, *(ExitList.begin())).ElementID;
20984  Utilities->CallLogPop(1571);
20985  return(" at " + ID);
20986  }
20987  else
20988  {
20989  Utilities->CallLogPop(1572);
20990  if(ExitList.size() < 4)
20991  {
20992  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
20993  return("");
20994  }
20995  else
20996  {
20997  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
20998  return("");
20999  }
21000  }
21001  }
21002  for(TNumListIterator ELIT = ExitList.begin(); ELIT != ExitList.end(); ELIT++)
21003  {
21004  if(Track->TrackElementAt(736, *ELIT).ActiveTrackElementName != StartName)
21005  {
21006  Utilities->CallLogPop(1570);
21007  if(ExitList.size() < 4)
21008  {
21009  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21010  return("");
21011  }
21012  else
21013  {
21014  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21015  return("");
21016  }
21017  }
21018  }
21019  Utilities->CallLogPop(1569);
21020  if(ExitList.size() < 4)
21021  {
21022  AllowedExits = ",\nallowable exit element(s): " + ExitLocList;
21023  return(" at " + StartName);
21024  }
21025  else
21026  {
21027  AllowedExits = ",\nallowable exit element(s):\n" + ExitLocList;
21028  return(" at " + StartName);
21029  }
21030 }
21031 
21032 // ---------------------------------------------------------------------------
21033 /* can't trust this as locations within a vector may not be contiguous
21034  bool TTrainController::IsServiceTerminating(int Caller, TTrainDataEntry *TDEPtr, TActionVectorEntry *AVPtr)
21035  {
21036  //Search ActionVector from the position after the entry value for Ptr to the end, and return true if find a Finish
21037  //entry before Fer or TimeLoc. No point checking for TimeTimeLoc since at a stop location now so a later TimeTimeLoc
21038  //must be preceded by a TimeLoc departure
21039  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",IsServiceTerminating");
21040  for(unsigned int x=1;x<TDEPtr->ActionVector.size();x++)
21041  {
21042  if((AVPtr + x) < TDEPtr->ActionVector.end())
21043  {
21044  AnsiString xx = (AVPtr + x)->Command;//test
21045  TTimetableFormatType xy = (AVPtr + x)->FormatType;//test
21046  TTimetableSequenceType xz = (AVPtr + x)->SequenceType;//test
21047  if(((AVPtr + x)->Command == "Fer") || ((AVPtr + x)->FormatType == TimeLoc))
21048  {
21049  Utilities->CallLogPop();
21050  return false;
21051  }
21052  else if((AVPtr + x)->SequenceType == FinishSequence)
21053  {
21054  Utilities->CallLogPop();
21055  return true;
21056  }
21057  }
21058  }
21059  Utilities->CallLogPop();
21060  return false;
21061  }
21062 */
21063 // ---------------------------------------------------------------------------
21064 
21065 void TTrainController::SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
21066 {
21067  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SendPerformanceSummary");
21068  AnsiString FormatStr = "####0.0";
21069  AnsiString AvLateArrMins = "";
21070  AnsiString AvEarlyArrMins = "";
21071  AnsiString AvLatePassMins = "";
21072  AnsiString AvEarlyPassMins = "";
21073  AnsiString AvLateDepMins = "";
21074  AnsiString AvLateExitMins = "";
21075  AnsiString AvEarlyExitMins = "";
21076 
21077  //calculate remaining CumulativeDelayedRandMinsAllTrains for trains still in vector (CumulativeDelayedRandMinsAllTrains for exited or removed trains already accounted for)
21078  for(unsigned int x = 0; x < TrainVector.size(); x++)
21079  {
21080  Utilities->CumulativeDelayedRandMinsAllTrains += int(TrainVectorAt(89, x).CumulativeDelayedRandMinsOneTrain);
21081  }
21082 
21083  if(LateArrivals > 0)
21084  {
21085  AvLateArrMins = FormatFloat(FormatStr, (TotLateArrMins / LateArrivals));
21086  }
21087  if(EarlyArrivals > 0)
21088  {
21089  AvEarlyArrMins = FormatFloat(FormatStr, (TotEarlyArrMins / EarlyArrivals));
21090  }
21091  if(LatePasses > 0)
21092  {
21093  AvLatePassMins = FormatFloat(FormatStr, (TotLatePassMins / LatePasses));
21094  }
21095  if(EarlyPasses > 0)
21096  {
21097  AvEarlyPassMins = FormatFloat(FormatStr, (TotEarlyPassMins / EarlyPasses));
21098  }
21099  if(LateDeps > 0)
21100  {
21101  AvLateDepMins = FormatFloat(FormatStr, (TotLateDepMins / LateDeps));
21102  }
21103  if(LateExits > 0) //added at v2.9.1
21104  {
21105  AvLateExitMins = FormatFloat(FormatStr, (TotLateExitMins / LateExits));
21106  }
21107  if(EarlyExits > 0) //added at v2.9.1
21108  {
21109  AvEarlyExitMins = FormatFloat(FormatStr, (TotEarlyExitMins / EarlyExits));
21110  }
21111  PerfFile << '\n' << '\n' << "***************************************";
21112  PerfFile << '\n' << '\n' << "Performance summary:" << '\n';
21113 
21114  if(OnTimeArrivals != 1)
21115  {
21116  PerfFile << OnTimeArrivals << " on-time arrivals" << '\n';
21117  }
21118  else
21119  {
21120  PerfFile << OnTimeArrivals << " on-time arrival" << '\n';
21121  }
21122  if(LateArrivals > 1)
21123  {
21124  PerfFile << LateArrivals << " late arrivals (average " << AvLateArrMins.c_str() << " min)" << '\n';
21125  }
21126  else if(LateArrivals == 1)
21127  {
21128  PerfFile << LateArrivals << " late arrival (" << AvLateArrMins.c_str() << " min)" << '\n';
21129  }
21130  else
21131  {
21132  PerfFile << LateArrivals << " late arrivals" << '\n';
21133  }
21134  if(EarlyArrivals > 1)
21135  {
21136  PerfFile << EarlyArrivals << " early arrivals (average " << AvEarlyArrMins.c_str() << " min)" << '\n';
21137  }
21138  else if(EarlyArrivals == 1)
21139  {
21140  PerfFile << EarlyArrivals << " early arrival (" << AvEarlyArrMins.c_str() << " min)" << '\n';
21141  }
21142  else
21143  {
21144  PerfFile << EarlyArrivals << " early arrivals" << '\n';
21145  }
21146  if(OnTimePasses != 1)
21147  {
21148  PerfFile << OnTimePasses << " on-time passes" << '\n';
21149  }
21150  else
21151  {
21152  PerfFile << OnTimePasses << " on-time pass" << '\n';
21153  }
21154  if(LatePasses > 1)
21155  {
21156  PerfFile << LatePasses << " late passes (average " << AvLatePassMins.c_str() << " min)" << '\n';
21157  }
21158  else if(LatePasses == 1)
21159  {
21160  PerfFile << LatePasses << " late pass (" << AvLatePassMins.c_str() << " min)" << '\n';
21161  }
21162  else
21163  {
21164  PerfFile << LatePasses << " late passes" << '\n';
21165  }
21166  if(EarlyPasses > 1)
21167  {
21168  PerfFile << EarlyPasses << " early passes (average " << AvEarlyPassMins.c_str() << " min)" << '\n';
21169  }
21170  else if(EarlyPasses == 1)
21171  {
21172  PerfFile << EarlyPasses << " early pass (" << AvEarlyPassMins.c_str() << " min)" << '\n';
21173  }
21174  else
21175  {
21176  PerfFile << EarlyPasses << " early passes" << '\n';
21177  }
21178 
21179  if(OnTimeExits != 1) //this batch added at v2.9.1
21180  {
21181  PerfFile << OnTimeExits << " on-time exits" << '\n';
21182  }
21183  else
21184  {
21185  PerfFile << OnTimeExits << " on-time exit" << '\n';
21186  }
21187  if(LateExits > 1)
21188  {
21189  PerfFile << LateExits << " late exits (average " << AvLateExitMins.c_str() << " min)" << '\n';
21190  }
21191  else if(LateExits == 1)
21192  {
21193  PerfFile << LateExits << " late exit (" << AvLateExitMins.c_str() << " min)" << '\n';
21194  }
21195  else
21196  {
21197  PerfFile << LateExits << " late exits" << '\n';
21198  }
21199  if(EarlyExits > 1)
21200  {
21201  PerfFile << EarlyExits << " early exits (average " << AvEarlyExitMins.c_str() << " min)" << '\n';
21202  }
21203  else if(EarlyExits == 1)
21204  {
21205  PerfFile << EarlyExits << " early exit (" << AvEarlyExitMins.c_str() << " min)" << '\n';
21206  }
21207  else
21208  {
21209  PerfFile << EarlyExits << " early exits" << '\n';
21210  }
21211 
21212  if(OnTimeDeps != 1)
21213  {
21214  PerfFile << OnTimeDeps << " on-time departures" << '\n';
21215  }
21216  else
21217  {
21218  PerfFile << OnTimeDeps << " on-time departure" << '\n';
21219  }
21220  if(LateDeps > 1)
21221  {
21222  PerfFile << LateDeps << " late departures (average " << AvLateDepMins.c_str() << " min)" << '\n';
21223  }
21224  else if(LateDeps == 1)
21225  {
21226  PerfFile << LateDeps << " late departure (" << AvLateDepMins.c_str() << " min)" << '\n';
21227  }
21228  else
21229  {
21230  PerfFile << LateDeps << " late departures" << '\n';
21231  }
21232  TDateTime TempExcessLCDownTime;
21233  for(unsigned int x = 0; x < Track->BarriersDownVector.size(); x++) //added at v2.6.0 - should have been added earlier
21234  {
21235 // if(Track->BarriersDownVector.at(x).ReducedTimePenalty) //assume train still to cross LC as probably will, else have false high value & can have
21236  //later perf summaries with lower values, changed at v2.8.0
21237 // {
21238  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime - TDateTime(180.0 / 86400);
21239 // }
21240 /*
21241  else
21242  {
21243  TempExcessLCDownTime = TrainController->TTClockTime - Track->BarriersDownVector.at(x).StartTime;
21244  }
21245 */
21246  if(TempExcessLCDownTime > TDateTime(0))
21247  {
21248  TrainController->ExcessLCDownMins += (double(TempExcessLCDownTime) * 1440);
21249  }
21250  }
21251 
21252  AnsiString FormattedExcessLCDownMins = FormatFloat(FormatStr, ExcessLCDownMins);
21253 
21254  if(ExcessLCDownMins > 0.1)
21255  {
21256  PerfFile << FormattedExcessLCDownMins.c_str() << " excess minutes of level crossing barrier down time" << '\n';
21257  }
21258  else
21259  {
21260  ExcessLCDownMins = 0; //added at v2.16.1 so doesn't count towards performance score if < 0.1mins (else can have low score with no excess mins recorded)
21261  }
21262  if(Utilities->CumulativeDelayedRandMinsAllTrains > 0) //added at v2.13.0
21263  {
21264  PerfFile << Utilities->CumulativeDelayedRandMinsAllTrains << " minutes lost due to random delays when stopped at locations" << '\n';
21265  }
21266  if(MissedStops != 1)
21267  {
21268  PerfFile << MissedStops << " missed stops" << '\n';
21269  }
21270  else
21271  {
21272  PerfFile << MissedStops << " missed stop" << '\n';
21273  }
21274  if(OtherMissedEvents != 1)
21275  {
21276  PerfFile << OtherMissedEvents << " other missed events" << '\n';
21277  }
21278  else
21279  {
21280  PerfFile << OtherMissedEvents << " other missed event" << '\n';
21281  }
21282  if(SkippedTTEvents != 1)
21283  {
21284  PerfFile << SkippedTTEvents << " skipped timetable events" << '\n';
21285  }
21286  else
21287  {
21288  PerfFile << SkippedTTEvents << " skipped timetable event" << '\n';
21289  }
21290  if(UnexpectedExits != 1)
21291  {
21292  PerfFile << UnexpectedExits << " unexpected train exits" << '\n';
21293  }
21294  else
21295  {
21296  PerfFile << UnexpectedExits << " unexpected train exit" << '\n';
21297  }
21298  if(IncorrectExits != 1)
21299  {
21300  PerfFile << IncorrectExits << " incorrect train exits" << '\n';
21301  }
21302  else
21303  {
21304  PerfFile << IncorrectExits << " incorrect train exit" << '\n';
21305  }
21306  if(NumFailures != 1)
21307  {
21308  PerfFile << NumFailures << " train failures" << '\n';
21309  }
21310  else
21311  {
21312  PerfFile << NumFailures << " train failure" << '\n';
21313  }
21314  if(AvHoursIntValue > 0)
21315  {
21316  if(AvHoursIntValue == 1)
21317  {
21318  PerfFile << AvHoursIntValue << " hour mean time betweeen train failures" << '\n';
21319  }
21320  else
21321  {
21322  PerfFile << AvHoursIntValue << " hours mean time betweeen train failures" << '\n';
21323  }
21324  }
21325  AnsiString AvLateMinsLocsNotReached = "";
21326 
21328  int LocsNotReached = (NotStartedTrainLateArr + OperatingTrainLateArr); //dropped divide by 2 after 2.7.0 as don't count late departures as 'failed to arrive'
21329  // each location has an arrival and departure (generally) so divide by 2 - no, dropped after 2.7.0
21330 
21331  if(LocsNotReached > 0)
21332  {
21333  AvLateMinsLocsNotReached = FormatFloat(FormatStr, (OperatingTrainLateMins + NotStartedTrainLateMins) / (NotStartedTrainLateArr + OperatingTrainLateArr));
21334  PerfFile << LocsNotReached << " locations that trains failed to reach (average lateness " << AvLateMinsLocsNotReached.c_str() << " min)" << '\n';
21335  }
21336  if(SPADRisks != 1)
21337  {
21338  PerfFile << SPADRisks << " SPAD risks" << '\n';
21339  }
21340  else
21341  {
21342  PerfFile << SPADRisks << " SPAD risk" << '\n';
21343  }
21344  if(SPADEvents != 1)
21345  {
21346  PerfFile << SPADEvents << " SPADs" << '\n';
21347  }
21348  else
21349  {
21350  PerfFile << SPADEvents << " SPAD" << '\n';
21351  }
21352  if(Derailments != 1)
21353  {
21354  PerfFile << Derailments << " derailments" << '\n';
21355  }
21356  else
21357  {
21358  PerfFile << Derailments << " derailment" << '\n';
21359  }
21360  if(CrashedTrains != 1)
21361  {
21362  PerfFile << CrashedTrains << " crashed trains" << '\n';
21363  }
21364  else
21365  {
21366  PerfFile << CrashedTrains << " crashed train" << '\n';
21367  }
21368  PerfFile << '\n' << "***************************************" << '\n';
21369 
21370  bool DerailSPADFlag = false, CrashFlag = false;
21371 
21372  int OverallScorePercent = 100;
21373  int TotArrDepExit = 0;
21374  double TotLateMinsFactor = 1;
21375  double MissedStopAndSPADRiskFactor = 1;
21376  double NetNegFactor = 1;
21377 
21379  EarlyExits + LateExits + OnTimeExits; //exits added at v2.9.1, passes not counted
21380  // TotArrDep: total number of arrivals & departures including those for trains that haven't reached their destinations yet and are late
21381  // changed at v1.1.4 - calc was inside "if(OverallScorePercent == 100).." block so could remain 0 for SPADs & crashes, & then received the
21382  // 'no timetabled departures... message, which was inappropriate
21383 
21384  if((SPADEvents > 0) || (Derailments > 0))
21385  {
21386  OverallScorePercent = 5; // overrides other calculations
21387  DerailSPADFlag = true;
21388  }
21389  if(CrashedTrains > 0)
21390  {
21391  OverallScorePercent = 0; // overrides other calculations
21392  CrashFlag = true;
21393  }
21394  if(OverallScorePercent == 100)
21395  {
21396  int LatenessPenalty = TotLateArrMins + TotLateDepMins; //added at v2.13.0 for random delays
21397  if(Utilities->CumulativeDelayedRandMinsAllTrains > LatenessPenalty)
21398  {
21399  LatenessPenalty = 0;
21400  }
21401  else
21402  {
21403  LatenessPenalty -= Utilities->CumulativeDelayedRandMinsAllTrains;
21404  }
21405  if(TotArrDepExit > 0)
21406  {
21407  TotLateMinsFactor = exp((-0.1732) * (LatenessPenalty + OperatingTrainLateMins + NotStartedTrainLateMins + TotLateExitMins +
21408  ((OtherMissedEvents + SkippedTTEvents + UnexpectedExits + ExcessLCDownMins) * 15)) / TotArrDepExit); //exits added at v2.9.1
21409  // TotLateMinsFactor: negative exponential factor based on overall average arr & dep minutes late (with OtherMissedEvents & UnexpectedExits
21410  // counting as 15 mins late each), where 4 mins late average = half, 8 mins late = a quarter etc
21411  MissedStopAndSPADRiskFactor = exp((-17.33) * (MissedStops + SPADRisks + IncorrectExits) / TotArrDepExit);
21412  // MissedEventAndSPADRiskFactor: negative exponential factor based on number of missed stops, SPAD risks & IncorrectExits as a proportion
21413  // of arrivals & departures, where 4% = half, 8% = a quarter etc
21414  NetNegFactor = TotLateMinsFactor * MissedStopAndSPADRiskFactor;
21415  // NetNegfactor: product of the above two
21416  OverallScorePercent = 100 * NetNegFactor;
21417  }
21418  }
21419  if((TotArrDepExit > 0) || DerailSPADFlag || CrashFlag)
21420  // flag condits added at v1.1.4 - see above for what the error was
21421  {
21422  AnsiString OneFailureString = ", though the failure would account for some poor performance";
21423  AnsiString TwoOrMoreFailureString = ", though the failures would account for some poor performance";
21424  AnsiString AddedString = "";
21425  if(NumFailures == 1)
21426  {
21427  AddedString = OneFailureString;
21428  }
21429  if(NumFailures > 1)
21430  {
21431  AddedString = TwoOrMoreFailureString;
21432  }
21433  PerfFile << "\nOverall score: " << OverallScorePercent << "%\n";
21434  AnsiString Rating = "";
21435  if(OverallScorePercent == 100)
21436  {
21437  Rating = "Perfect!";
21438  }
21439  else if(OverallScorePercent >= 95)
21440  {
21441  Rating = "Excellent";
21442  }
21443  else if(OverallScorePercent >= 90)
21444  {
21445  Rating = "Very good";
21446  }
21447  else if(OverallScorePercent >= 80)
21448  {
21449  Rating = "Good";
21450  }
21451  else if(OverallScorePercent >= 70)
21452  {
21453  Rating = "Fair";
21454  }
21455  else if(OverallScorePercent >= 60)
21456  {
21457  Rating = "Unacceptable" + AddedString;
21458  }
21459  else if(OverallScorePercent >= 50)
21460  {
21461  Rating = "Poor" + AddedString;
21462  }
21463  else if(OverallScorePercent >= 40)
21464  {
21465  Rating = "Bad" + AddedString;
21466  }
21467  else if(OverallScorePercent >= 30)
21468  {
21469  Rating = "Very bad" + AddedString;
21470  }
21471  else if(OverallScorePercent >= 20)
21472  {
21473  Rating = "Terrible" + AddedString;
21474  }
21475  else if(OverallScorePercent >= 10)
21476  {
21477  Rating = "Appalling" + AddedString;
21478  }
21479  else if(OverallScorePercent >= 5)
21480  {
21481  if(DerailSPADFlag)
21482  {
21483  Rating = "Disastrous - potential loss of life";
21484  }
21485  // SPADs/Derailments
21486  else
21487  {
21488  Rating = "Dire" + AddedString;
21489  }
21490  }
21491  else if(OverallScorePercent < 5)
21492  {
21493  if(CrashFlag)
21494  {
21495  Rating = "Catastrophic - loss of life"; // Crashes
21496  }
21497  else
21498  {
21499  Rating = "Abysmal";
21500  }
21501  }
21502  PerfFile << "Overall rating: " << Rating.c_str() << '\n';
21503  }
21504  else
21505  {
21506  PerfFile << "\nThere were no timetabled departures, arrivals or exits so there is insufficient information to provide a performance score or rating" << '\n';
21507  }
21508  PerfFile << '\n' << "***************************************";
21509  PerfFile.flush();
21510  Utilities->CallLogPop(1736);
21511 }
21512 
21513 // ---------------------------------------------------------------------------
21514 
21516 {
21517  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",SetWarningFlags");
21518  for(unsigned int x = 0; x < TrainVector.size(); x++)
21519  {
21520  TTrain &Train = TrainVectorAt(58, x);
21521  if(Train.Crashed)
21522  // can't use background colours for crashed & derailed because same colour
21523  {
21524  CrashWarning = true;
21525  }
21526  else if(Train.Derailed)
21527  // can't use background colours for crashed & derailed because same colour
21528  {
21529  DerailWarning = true;
21530  }
21531  else if(Train.BackgroundColour == clSPADBackground)
21532  // use colour as that changes as soon as passes signal
21533  {
21534  SPADWarning = true;
21535  }
21536  else if(Train.BackgroundColour == clTrainFailedBackground)
21537  {
21538  TrainFailedWarning = true;
21539  }
21540  else if(Train.BackgroundColour == clCallOnBackground)
21541  // use colour as also stopped at signal
21542  {
21543  CallOnWarning = true;
21544  }
21545  else if(Train.BackgroundColour == clSignalStopBackground)
21546  // use colour to distinguish from call-on
21547  {
21548  SignalStopWarning = true;
21549  }
21550  else if(Train.BackgroundColour == clBufferAttentionNeeded)
21551  // use colour to distinguish from ordinary buffer stop
21552  {
21553  BufferAttentionWarning = true;
21554  }
21555  }
21556  Utilities->CallLogPop(1796);
21557 }
21558 
21559 // ---------------------------------------------------------------------------
21560 
21562 {
21563  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",CalcSignalStopLateness");
21564 
21565  // calculate lateness for running trains
21568  for(unsigned int x = 0; x < TrainVector.size(); x++)
21569  {
21570  TTrain &Train = TrainVectorAt(64, x);
21571  for(TActionVectorEntry * AVEntryPtr = &Train.TrainDataEntryPtr->ActionVector.front(); AVEntryPtr < &Train.TrainDataEntryPtr->ActionVector.back();
21572  AVEntryPtr++)
21573  {
21574  if(AVEntryPtr < Train.ActionVectorEntryPtr)
21575  {
21576  continue;
21577  }
21578  if((AVEntryPtr->ArrivalTime > TDateTime(-1)) && !Train.RevisedStoppedAtLoc() && (GetRepeatTime(42, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes) <
21579  TTClockTime))
21580  {
21581  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(43, AVEntryPtr->ArrivalTime, Train.RepeatNumber, Train.IncrementalMinutes));
21583  }
21584 /* dropped departures after 2.7.0 because these don't count for 'failed to reach' numbers
21585  if((AVEntryPtr->DepartureTime > TDateTime(-1)) && (GetRepeatTime(44, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes) <
21586  TTClockTime))
21587  {
21588  OperatingTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(45, AVEntryPtr->DepartureTime, Train.RepeatNumber, Train.IncrementalMinutes));
21589  OperatingTrainArrDep++;
21590  }
21591 */
21592  }
21593  }
21594 
21595  // calculate lateness for trains that haven't started yet (could be held awaiting entry)
21598 
21599  for(unsigned int x = 0; x < TrainDataVector.size(); x++)
21600  {
21601  TTrainDataEntry & TDEntry = TrainDataVector.at(x);
21602  const TActionVectorEntry &AVEntryLast = TDEntry.ActionVector.at(TDEntry.ActionVector.size() - 1);
21603  int IncrementalMinutes = 0;
21604  if(AVEntryLast.FormatType == Repeat)
21605  {
21606  IncrementalMinutes = AVEntryLast.RearStartOrRepeatMins;
21607  }
21608  for(int y = 0; y < TDEntry.NumberOfTrains; y++)
21609  {
21610  TTrainOperatingData &TTOD = TDEntry.TrainOperatingDataVector.at(y);
21611  if(TTOD.RunningEntry != NotStarted)
21612  {
21613  continue;
21614  }
21615  // note that can't rely on the above for sessionfiles saved before v0.6b as wasn't set to Running for Sns/Fsp/rsp & shuttles
21616  // but if trains had exited then would be set to Exited, so need to check against trains still operating - use the test below
21617  bool TrainOperatingFlag = false;
21618  for(unsigned int a = 0; a < TrainController->TrainVector.size(); a++)
21619  {
21620  if((TrainController->TrainVector.at(a).TrainDataEntryPtr == &TDEntry) && (TrainController->TrainVector.at(a).RepeatNumber == y))
21621  {
21622  TrainOperatingFlag = true;
21623  break;
21624  }
21625  }
21626  if(TrainOperatingFlag)
21627  {
21628  continue;
21629  }
21630  if(GetRepeatTime(46, TDEntry.ActionVector.at(0).EventTime, y, IncrementalMinutes) > TTClockTime)
21631  {
21632  break; // if the first time is greater than TTClockTime then all the rest will also be greater (& default of -1 will be less so will be ignored)
21633  }
21634  for(unsigned int z = 0; z < TDEntry.ActionVector.size(); z++)
21635  {
21636  TActionVectorEntry &AVEntry = TDEntry.ActionVector.at(z);
21637  if(GetRepeatTime(35, AVEntry.EventTime, y, IncrementalMinutes) > TTClockTime)
21638  {
21639  break; // all the rest will also be greater (& default of -1 will be less)
21640  }
21641  if(GetRepeatTime(36, AVEntry.ArrivalTime, y, IncrementalMinutes) > TTClockTime)
21642  {
21643  break; // all the rest will also be greater (& default of -1 will be less)
21644  }
21645  if(GetRepeatTime(37, AVEntry.DepartureTime, y, IncrementalMinutes) > TTClockTime)
21646  {
21647  break; // all the rest will also be greater (& default of -1 will be less)
21648  }
21649  if((AVEntry.ArrivalTime > TDateTime(-1)) && (GetRepeatTime(38, AVEntry.ArrivalTime, y, IncrementalMinutes) < TTClockTime))
21650  {
21651  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(39, AVEntry.ArrivalTime, y, IncrementalMinutes));
21653  }
21654 /* dropped departures after 2.7.0 as only interested in 'failed to reach' number - if train hasn't arrived then it hasn't departed so shouldn't count that as part of 'failed to reach'
21655  if((AVEntry.DepartureTime > TDateTime(-1)) && (GetRepeatTime(40, AVEntry.DepartureTime, y, IncrementalMinutes) < TTClockTime))
21656  {
21657  NotStartedTrainLateMins += 1440 * double(TTClockTime - GetRepeatTime(41, AVEntry.DepartureTime, y, IncrementalMinutes));
21658  NotStartedTrainArrDep++;
21659  }
21660 */
21661  }
21662  }
21663  }
21664  Utilities->CallLogPop(1894);
21665 }
21666 
21667 // ---------------------------------------------------------------------------
21668 
21670 // new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2.13.0)
21671 // clears entries then adds values for running trains then for continuation entries
21672 // dont limit size here as need to check all trains (ActionsDueListBox is limited to 20 trains in Interface.cpp)
21673 {
21674  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildOpTimeToActMultimap");
21675  OpTimeToActMultiMap.clear();
21676  TOpTimeToActMultiMapEntry OpTimeToActMultiMapEntry;
21677 
21678  if(!TrainVector.empty())
21679  // build OpTimeToActMultiMap entries for running trains
21680  {
21681  AnsiString HeadCode;
21682  // dropped in favour of TrainID for running trains int VecPos; //TrackVectorPosition of LeadElement or continuation where train is to enter
21683  int TrainID;
21684  THCandTrainPosParam HCandTrainPosParam;
21685  for(unsigned int x = 0; x < TrainVector.size(); x++)
21686  {
21687  HeadCode = TrainVectorAt(62, x).HeadCode;
21688  TrainID = TrainVectorAt(63, x).TrainID;
21689  HCandTrainPosParam.first = HeadCode;
21690  HCandTrainPosParam.second = TrainID;
21691  float TimeToAct = TrainVectorAt(65, x).OpTimeToAct;
21692  if((TimeToAct >= 0) && (TimeToAct < 59.9))
21693  // -1 indicates don't display
21694  {
21695  OpTimeToActMultiMapEntry.first = TimeToAct;
21696  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
21697  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
21698  }
21699  }
21700  }
21701 /*
21702  * class TContinuationTrainExpectationEntry
21703  {
21704  public:
21705  AnsiString Description; ///< service description
21706  AnsiString HeadCode; ///< service headcode
21707  int RepeatNumber; ///< service RepeatNumber
21708  int IncrementalMinutes; ///< Repeat separation in minutes
21709  int IncrementalDigits; ///< Repeat headcode separation
21710  int VectorPosition; ///< TrackVectorPosition for the continuation element
21711  TTrainDataEntry *TrainDataEntryPtr; ///< points to the service entry in the timetable's TrainDataVector
21712  };
21713 
21714  Multimap class for TContinuationTrainExpectationEntry objects, where the access key is the expectation time
21715  typedef std::multimap<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMap;
21716  typedef TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator; ///< iterator for the multimap
21717  typedef std::pair<TDateTime, TContinuationTrainExpectationEntry> TContinuationTrainExpectationMultiMapPair; ///< a single multimap entry
21718 */
21719 
21721  // build OpTimeToActMultiMap entries for expected trains
21722  {
21723  // note that using the ContinuationTrainExpectationMultiMap automatically ensures that entries are in ascending time order
21724  // first have to calculate times to red signal for each train due to enter (ignore later trains as will likely change before they are due)
21725  float TimeToAct = 0; // minutes
21726  int DistanceToRedSignal = 0; // metres
21727  TContinuationEntryVecPosVector ContinuationEntryVecPosVector;
21728  // used to ensure only one train displayed for a given continuation
21729  ContinuationEntryVecPosVector.clear();
21730  bool LaterTrain = false;
21733  {
21734  LaterTrain = false;
21735  if(CTEIt->second.TrainDataEntryPtr->TrainOperatingDataVector.at(CTEIt->second.RepeatNumber).RunningEntry != NotStarted)
21736  {
21737  CTEIt++;
21738  continue; // not interested in running or exited trains
21739  }
21740  if(Track->TrackElementAt(934, CTEIt->second.VectorPosition).TrainIDOnElement > 0)
21741  {
21742  CTEIt++;
21743  continue;
21744  // don't include trains not entered yet when a train is already on the continuation
21745  }
21746  if(!ContinuationEntryVecPosVector.empty())
21747  {
21748  for(unsigned int x = 0; x < ContinuationEntryVecPosVector.size(); x++)
21749  {
21750  if(CTEIt->second.VectorPosition == ContinuationEntryVecPosVector.at(x))
21751  {
21752  LaterTrain = true;
21753  ;
21754  // skip past remaining trains waiting to enter at same point
21755  break;
21756  }
21757  }
21758  }
21759  if(LaterTrain)
21760  {
21761  CTEIt++;
21762  continue;
21763  }
21764  ContinuationEntryVecPosVector.push_back(CTEIt->second.VectorPosition);
21765  AnsiString HeadCode = CTEIt->second.HeadCode;
21766  float CurrentStopTime; // set to 0 at start of function
21767  float LaterStopTime; // set to 0 at start of function
21768  float RecoverableTime; // set to 0 at start of function
21769  int AvTrackSpeed; // set to 0 at start of function
21770  int TrainID = -1; // not yet allocated for train still to enter
21771  int DistanceToExit; //not used for continuation entries
21772  THVShortPair ExitPair;
21773  bool SigControlAndCanPassRedSignal = false; // doesn't apply for a continuation
21774 
21775 //at v2.11.0 found that with *AVPtr set to ...ActionVector.at(0) below instead of ...at(1) to stop signaller control trains throwing an error (because there is no ...at(1) -
21776 //discovered with Birmingham) the LaterStopTime isn't calculated and if a train does something other than depart after an arrival it is still listed in the actions due panel -
21777 //because it just calcs the distance to the red signal and converts that to a time. So here (after v2.11.0) this new test is introduced to determine whether a train is a
21778 //signaller control train (when the ActionVector size is 1) or not (ActionVector size > 1), and the ...at(value) is set accordingly - 0 for signaller control or 1 if not.
21779 
21780  int AtValue = 1;
21781  if(CTEIt->second.TrainDataEntryPtr->ActionVector.size() == 1)
21782  {
21783  AtValue = 0;
21784  }
21785  DistanceToRedSignal = CalcDistanceToRedSignalandStopTime(1, CTEIt->second.VectorPosition, 0,
21786  // EntryPos always 0 for entering at a continuation
21787  SigControlAndCanPassRedSignal, &CTEIt->second.TrainDataEntryPtr->ActionVector.at(AtValue), //see above note
21788  HeadCode, TrainID, CurrentStopTime, LaterStopTime, RecoverableTime, AvTrackSpeed, DistanceToExit, ExitPair);
21789  // for above VectorPosition is the first element to have its length included in the sum, so for a continuation it's the continuation itself
21790  // for a train it's the one in front of LeadElement
21791  if(AvTrackSpeed < 30)
21792  {
21793  AvTrackSpeed = 30;
21794  }
21795  if(DistanceToRedSignal == -1)
21796  {
21797  TimeToAct = 60.0;
21798  }
21799  else
21800  {
21801  int Speed = AvTrackSpeed;
21802  int MaxSpeed = int(CTEIt->second.TrainDataEntryPtr->MaxRunningSpeed);
21803  if(AvTrackSpeed > MaxSpeed)
21804  {
21805  Speed = MaxSpeed;
21806  }
21807  if(CTEIt->second.TrainDataEntryPtr->ActionVector.at(0).SignallerControl) //changed to ...at(0) from at(1) at v2.11.0 as SignallerControl only valid for ..at(0)
21808  // defined in timetable as under signaller control
21809  {
21810  Speed = CTEIt->second.TrainDataEntryPtr->SignallerSpeed;
21811  LaterStopTime = 0;
21812  }
21813  TimeToAct = LaterStopTime + DistanceToRedSignal * 3.6 / 60 / Speed;
21814  // accel & decel taken into account in
21815  // CalcDistanceToRedSignalandStopTime
21816  // 3.6 convertsKm/h to m/s & 60 converts seconds to minutes
21817  // don't need CurrentStopTime or RecoverableTime for continuation entries
21818  float MinsBefEnter = double(CTEIt->first - TTClockTime) * 86400.0 / 60.0;
21819  TimeToAct += MinsBefEnter;
21820  }
21821  THCandTrainPosParam HCandTrainPosParam;
21822  HCandTrainPosParam.first = HeadCode;
21823  HCandTrainPosParam.second = -1 - CTEIt->second.VectorPosition;
21824  // -1-CTE... because 2nd value covers TrainID if +ve &
21825  // continuation track vector position if -ve, -1 allows for vecpos being 0
21826  if(TimeToAct < 59.9) // if 60 don't enter a value in multimap
21827  {
21828  OpTimeToActMultiMapEntry.first = TimeToAct;
21829  OpTimeToActMultiMapEntry.second = HCandTrainPosParam;
21830  OpTimeToActMultiMap.insert(OpTimeToActMultiMapEntry);
21831  }
21832  CTEIt++;
21833  }
21834  }
21835  Utilities->CallLogPop(2081);
21836 }
21837 
21838 // ---------------------------------------------------------------------------
21839 
21841 // new for multiplayer
21842 // clears entries then adds values for running trains
21843 {
21844  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",RebuildTimeToExitMultiMap");
21845  TimeToExitMultiMap.clear();
21846  TTimeToExitMultiMapEntry TimeToExitMultiMapEntry;
21847 
21848  if(!TrainVector.empty())
21849  // build map entries for running trains
21850  {
21851  TExitInfo ExitInfo; //corresponds to TServiceInfo in Interface
21852  THVShortPair ExitPair;
21853  float TimeToExit;
21854  for(unsigned int x = 0; x < TrainVector.size(); x++)
21855  {
21857  ExitInfo.RepeatNumber = short(TrainVectorAt(81, x).RepeatNumber);
21858  ExitInfo.TimeToExitSecs = short(TrainVectorAt(77, x).TimeToExit * 60);
21859  ExitPair = TrainVectorAt(76, x).ExitPair;
21860  if((ExitInfo.TimeToExitSecs >= 3570) || (ExitInfo.TimeToExitSecs < 1)) //59.5 mins or -60 secs
21861  {
21862  ExitInfo.TimeToExitSecs = -1;
21863  }
21864  TimeToExitMultiMapEntry.first = ExitPair;
21865  TimeToExitMultiMapEntry.second = ExitInfo;
21866  TimeToExitMultiMap.insert(TimeToExitMultiMapEntry);
21867  }
21868  }
21869  Utilities->CallLogPop(2323);
21870 }
21871 
21872 // ---------------------------------------------------------------------------
21873 
21874 int TTrainController::CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos,
21875  bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime,
21876  float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
21877 // new v2.2.0
21878 // vectorPosition is the value for the first element to be measured - for a continuation it's the continuation itself, for a train
21879 // it's the one after LeadElement, returns -1 for infinity - i.e. not approaching red signal (e.g. maybe cdt before reach it).
21880 // CurrentStopTime is the time to depart from the current station (if stopped at a station) and LaterStopTime is the total station
21881 // stop times for stations after the current one. DistanceToRedSignal is what the name implies, if -1 is returned the other values
21882 // aren't used - this means there is no display for the train in question
21883 {
21884  Utilities->CallLog.push_back(Utilities->TimeStamp() + "," + AnsiString(Caller) + ",DistanceToRedSignal, " + AnsiString(TrackVectorPosition) + ", " +
21885  AnsiString(TrackVectorPositionEntryPos) + ", " + AVPtr->Command);
21886  int DistanceToRedSignal = 0;
21887  DistanceToExit = -1;
21888  ExitPair.first = -1;
21889  ExitPair.second = -1;
21890  int CumTrackSpeed = 0;
21891  // average track speed, in case need to use in time calc
21892  int TrackSpeedCount = 0;
21893  float KmPerLocationStop;
21894  float MaxAllowableSpeed;
21895 
21896  //below added at v2.6.1
21897  if(TrainID > -1) //will be -1 for trains not entered yet
21898  {
21899  TTrain &Train = TrainVectorAtIdent(51, TrainID);
21900  Train.DistanceToStationStop = 0; //if find a red signal first then this distance isn't needed
21901  Train.StationStopCalculated = false;
21902  }
21903  AvTrackSpeed = 0;
21904  int CurrentElement = TrackVectorPosition;
21905  int CurrentEntryPos = TrackVectorPositionEntryPos;
21906  int NextElement;
21907  int NextEntryPos;
21908  int NextExitPos;
21909 
21910  CurrentStopTime = 0;
21911  LaterStopTime = 0;
21912  RecoverableTime = 0;
21913  if(CurrentElement == -1) // train on end element, no action needed
21914  {
21915  Utilities->CallLogPop(2094);
21916  return(-1);
21917  }
21918  int CurrentExitPos;
21919 
21920  // get ExitPos for first element to be measured
21921  if(Track->TrackElementAt(935, CurrentElement).TrackType == Points)
21922  {
21923  if((CurrentEntryPos == 0) || (CurrentEntryPos == 2)) // leading point
21924  {
21925  if(Track->TrackElementAt(936, CurrentElement).Attribute == 0)
21926  {
21927  CurrentExitPos = 1;
21928  }
21929  else
21930  {
21931  CurrentExitPos = 3;
21932  }
21933  }
21934  else
21935  {
21936  CurrentExitPos = 0; // trailing point
21937  }
21938  }
21939  else
21940  {
21941  CurrentExitPos = Track->GetNonPointsOppositeLinkPos(CurrentEntryPos);
21942  }
21943  // get CumTrackSpeed for first measured element
21944 
21945  TConfiguration CurrentExitConfig = Track->TrackElementAt(937, CurrentElement).Config[CurrentExitPos];
21946  int CurrentAttribute = Track->TrackElementAt(938, CurrentElement).Attribute;
21947  bool CurrentElementFailed = Track->TrackElementAt(1549, CurrentElement).Failed; //added at v2.13.2
21948 
21949  // check if currently stopped at a location, and if so add the remaining dwell time
21950  // can't use CurrentElement as that is in front of LeadElement and might not be at the location
21951  if(TrainID > -1)
21952  // -1 for a continuation and can't be at a location as not yet entered
21953  {
21954  TTrain &Train = TrainVectorAtIdent(39, TrainID); //Train wasn't a reference before v2.6.1 mods so FirstLaterStopRecoverableTime wouldn't be reset for the referenced train
21956  // this used to deduct from RecoverableTime when arrive at a location
21957  if(Train.RevisedStoppedAtLoc())
21958  {
21959  if(Train.StoppedForTrainInFront || Train.TrainInFront)
21960  {
21961  Utilities->CallLogPop(2082);
21962  return(-1); // no action needed
21963  }
21965  { //added '|| (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)' at v2.16.1 so description ignored in calculating action due time
21966  Utilities->CallLogPop(2083);
21967  return(-1); // not due a departure or a description change so no action needed
21968  }
21969  else if(((Train.ActionVectorEntryPtr + 1)->FormatType == TimeLoc) && (Train.ActionVectorEntryPtr->FormatType == TimeCmdDescription)) // due a departure immediately after change of description
21970  { //added at v2.16.1 to cover description change due next then a departure
21971  double TimeToDepart = double((Train.GetTrainTime(68, (Train.ActionVectorEntryPtr + 1)->DepartureTime)) - TrainController->TTClockTime) * 86400 / 60; // mins to depart excluding possible 30sec allowance from LastActionTime
21972  //need repeat time for the above
21973  if((Train.ActionVectorEntryPtr + 1)->DepartureTime == Train.ActionVectorEntryPtr->EventTime) //don't need repeat time here
21974  {
21975  TimeToDepart+= 0.5; //add in the 30 secs if depature time same as description change time
21976  }
21977  if(TimeToDepart < 0.5)
21978  {
21979  TimeToDepart = 0.5;
21980  }
21981  // can't convert a TDateTime to a float directly
21982  CurrentStopTime = float(TimeToDepart);
21983  AVPtr++;
21984  AVPtr++;
21985  }
21986  else if((Train.ActionVectorEntryPtr->FormatType == TimeLoc) || (Train.ActionVectorEntryPtr->FormatType == TimeTimeLoc)) // due a departure as next action
21987  {
21988  double TimeToDepart = double(Train.ReleaseTime - TrainController->TTClockTime) * 86400 / 60; // mins to depart
21989  // can't convert a TDateTime to a float directly
21990  CurrentStopTime = float(TimeToDepart);
21991  AVPtr++;
21992  }
21993  else //added at v2.16.1 to catch all other combinations
21994  { //none of the above so no action needed
21995  Utilities->CallLogPop(2628);
21996  return(-1);
21997  }
21998  }
21999  }
22000  // check if CurrentElement is a red signal, but ok if autosig route after provided signal not failed
22001  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22002  // ok if autosig route after red signal unless signal has failed
22003  {
22004  int NextElement = Track->TrackElementAt(939, CurrentElement).Conn[CurrentExitPos];
22005  int NextEntryPos = Track->TrackElementAt(940, CurrentElement).ConnLinkPos[CurrentExitPos];
22006  int RouteNumber; // holder for referenced value, not used
22007  if((AllRoutes->GetRouteTypeAndNumber(33, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22008  { //CurrentElementFailed added at v2.13.2
22009  Utilities->CallLogPop(2078);
22010  return(-1);
22011  }
22012  else if(SigControlAndCanPassRedSignal)
22013  // ignore signal and increment CurrentElement to NextElement
22014  {
22015  if(Track->TrackElementAt(941, NextElement).TrackType == Points)
22016  {
22017  if((NextEntryPos == 0) || (NextEntryPos == 2))
22018  // leading entry point
22019  {
22020  if(Track->TrackElementAt(942, NextElement).Attribute == 0)
22021  {
22022  NextExitPos = 1;
22023  }
22024  else
22025  {
22026  NextExitPos = 3;
22027  }
22028  }
22029  else
22030  {
22031  NextExitPos = 0; // trailing entry point
22032  }
22033  }
22034  else
22035  {
22036  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22037  }
22038  CurrentElement = NextElement;
22039  CurrentEntryPos = NextEntryPos;
22040  CurrentExitPos = NextExitPos;
22041  CurrentExitConfig = Track->TrackElementAt(943, CurrentElement).Config[CurrentExitPos];
22042  CurrentAttribute = Track->TrackElementAt(944, CurrentElement).Attribute;
22043  }
22044  else if((TrainID > -1) && (TrainVectorAtIdent(40, TrainID).TrainMode == Timetable)) // ignore signallercontrol or will
22045  // give 'NOW' indication after allowed to pass stop signal when LeadMidLag (AllowedToPassRedSignal reset by this point)
22046  {
22047  Utilities->CallLogPop(2084);
22048  return(0);
22049  // stopped with red signal in front, don't need AvSpeedLimit in this case, & if at location awaiting departure dwell time already calculated
22050  }
22051  }
22052  int LaterStopNumber = 0;
22053  int x = 0;
22054  // added in v2.4.0 to prevent endless circling round track loops - spotted by Xeon 09/03/20 & reported by emsil
22055 
22056  while(!((CurrentExitConfig == Signal) && (CurrentAttribute == 0)))
22057  // not red signal next (in fwd direction) so enter loop to calc CumLength
22058  {
22059  x++; // added in v2.4.0 as above
22060  if(x > 5000)
22061  {
22062  Utilities->CallLogPop(2120);
22063  return(-1);
22064  }
22065  if(CurrentEntryPos > 1)
22066  {
22067  DistanceToRedSignal += Track->TrackElementAt(916, CurrentElement).Length23;
22068  CumTrackSpeed += Track->TrackElementAt(945, CurrentElement).SpeedLimit23;
22069  }
22070  else
22071  {
22072  DistanceToRedSignal += Track->TrackElementAt(917, CurrentElement).Length01;
22073  CumTrackSpeed += Track->TrackElementAt(946, CurrentElement).SpeedLimit01;
22074  }
22075  TrackSpeedCount++;
22076 
22077  //added for multiplayer - exiting at a continuation and continuation length already added
22078  if((Track->TrackElementAt(1407, CurrentElement).TrackType == Continuation) && (Track->TrackElementAt(1408, CurrentElement).Config[CurrentExitPos] == End))
22079  {
22080  DistanceToExit = DistanceToRedSignal; //don't need to exit function here as will exit when find that the next Conn value is -1
22081  ExitPair.first = Track->TrackElementAt(1409, CurrentElement).HLoc;
22082  ExitPair.second = Track->TrackElementAt(1410, CurrentElement).VLoc;
22083  //here repeat calcs for MaxAllowableSpeed & AvTrackSpeed as done at end for stop signal
22084  //need here as next element will be -1 so will exit before calcs at end
22085  if(TrackSpeedCount > 0)
22086  {
22087  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22088  }
22089  else // shouldn't reach here but include to prevent divide by zero error
22090  {
22091  if(CurrentEntryPos > 1)
22092  {
22093  MaxAllowableSpeed = Track->TrackElementAt(951, CurrentElement).SpeedLimit23;
22094  }
22095  else
22096  {
22097  MaxAllowableSpeed = Track->TrackElementAt(952, CurrentElement).SpeedLimit01;
22098  }
22099  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22100  }
22101  //calc AvTrackSpeed
22102  if(LaterStopNumber > 0)
22103  {
22104  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22105  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22106  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22107  // average line speed/2 (for half distance accelerating and half decelerating.
22108  }
22109  else
22110  {
22111  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22112  // using linear trendline for accel & decel distance at various speeds
22113  // at half braking, speed never < 60 using this
22114  }
22115  if(AvTrackSpeed > MaxAllowableSpeed)
22116  {
22117  AvTrackSpeed = MaxAllowableSpeed;
22118  }
22119  }
22120 
22121  // added at v2.6.1 to find DistanceToStationStop for trains running early
22122  if(TrainID > -1) //can ignore continuation entries as these don't run early
22123  {
22124  TTrain &Train = TrainVectorAtIdent(52, TrainID);
22125  if(!Train.StationStopCalculated)
22126  {
22127  if(Train.TrainMode == Timetable)
22128  {
22129  bool StopRequired = false;
22130  if(!Train.TimetableFinished && (Train.NameInTimetableBeforeCDT(16, Track->TrackElementAt(1005, CurrentElement).ActiveTrackElementName,
22131  StopRequired) > -1) && ((Track->TrackElementAt(1006, CurrentElement).StationEntryStopLinkPos1 == CurrentEntryPos) ||
22132  (Track->TrackElementAt(1010, CurrentElement).StationEntryStopLinkPos2 == CurrentEntryPos) ||
22133  (Track->TrackElementAt(1654, CurrentElement).StationEntryStopLinkPos3 == CurrentEntryPos) ||
22134  (Track->TrackElementAt(1655, CurrentElement).StationEntryStopLinkPos4 == CurrentEntryPos)))
22135  {
22136  // no need to add in the length of element to CumulativeLength
22137  if(StopRequired)
22138  {
22139  Train.DistanceToStationStop = DistanceToRedSignal; // DistanceToRedSignal holds the intermediate distance to this point
22140  Train.StationStopCalculated = true; //don't want to update it with later stops
22141  }
22142  }
22143  }
22144  }
22145  }
22146  // check for train in front, but if on a bridge on other track then ok
22147  TTrackElement TE = Track->TrackElementAt(947, CurrentElement);
22148  int TrainOnElement;
22149  if(TE.TrackType != Bridge)
22150  {
22151  TrainOnElement = TE.TrainIDOnElement;
22152  }
22153  else
22154  {
22155  if(CurrentEntryPos > 1)
22156  {
22158  }
22159  else
22160  {
22162  }
22163  }
22164  if((TrainOnElement > -1) && (TrainOnElement != TrainID))
22165  // train in front before red signal
22166  {
22167  Utilities->CallLogPop(2085);
22168  return(-1);
22169  }
22170  // add to stoptime if required
22171  if(Track->TrackElementAt(948, CurrentElement).ActiveTrackElementName != "")
22172  {
22173  double StopTimeDouble;
22174  while(AVPtr->FormatType == PassTime)
22175  {
22176  AVPtr++; // skip past any passes
22177  }
22178  if((Track->TrackElementAt(949, CurrentElement).ActiveTrackElementName == AVPtr->LocationName) && ((AVPtr->FormatType == TimeLoc) ||
22179  (AVPtr->FormatType == TimeTimeLoc)))
22180  // stop due here so calc dwell time & advance Ptr
22181  {
22182  if(AVPtr->FormatType == TimeTimeLoc)
22183  {
22184  LaterStopNumber++;
22185  StopTimeDouble = double(AVPtr->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0;
22186  if(StopTimeDouble < 0.5)
22187  {
22188  StopTimeDouble = 0.5;
22189  }
22190  // at least 30 secs delay at station
22191  // can't convert a TDateTime to a float directly
22192  LaterStopTime += float(StopTimeDouble);
22193  RecoverableTime += StopTimeDouble - 0.5;
22194  if((LaterStopNumber == 1) && (TrainID > -1))
22195  {
22196  TrainVectorAtIdent(41, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22197  }
22198  AVPtr++;
22199  }
22200  else if((AVPtr->FormatType == TimeLoc) && (AVPtr->ArrivalTime != TDateTime(-1))) // must be an arrival
22201  {
22202  if((AVPtr + 1)->FormatType == TimeLoc)
22203  // must be a departure
22204  {
22205  LaterStopNumber++;
22206  StopTimeDouble = double((AVPtr + 1)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
22207  // can't convert a TDateTime to a float directly //so repeat times not required
22208  if(TrainID > -1) //exclude trains still to enter
22209  {
22210  TTrain &Train = TrainVectorAtIdent(67, TrainID);
22211  if(TTClockTime > Train.GetTrainTime(69, AVPtr->ArrivalTime)) //running late, added at v2.16.1
22212  {
22213  StopTimeDouble = double(Train.GetTrainTime(70, (AVPtr + 1)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
22214  // can't convert a TDateTime to a float directly
22215  }
22216  }
22217  if(StopTimeDouble < 0.5)
22218  {
22219  StopTimeDouble = 0.5;
22220  }
22221  // at least 30 secs delay at station
22222  LaterStopTime += float(StopTimeDouble);
22223  RecoverableTime += StopTimeDouble - 0.5;
22224  if((LaterStopNumber == 1) && (TrainID > -1))
22225  {
22226  TrainVectorAtIdent(42, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22227  }
22228  AVPtr++;
22229  AVPtr++;
22230  }
22231  else if(((AVPtr + 1)->FormatType == TimeCmdDescription) && ((AVPtr + 2)->FormatType == TimeLoc)) //change of description then departure
22232  { //added at v2.16.1 so description changes ignored in calculating time to act
22233  LaterStopNumber++;
22234  StopTimeDouble = double((AVPtr + 2)->DepartureTime - AVPtr->ArrivalTime) * 86400.0 / 60.0; //diff will be same for all repeats
22235  // can't convert a TDateTime to a float directly //so repeat times not required
22236  if(TrainID > -1) //exclude trains still to enter
22237  {
22238  TTrain &Train = TrainVectorAtIdent(68, TrainID);
22239  if(TTClockTime > Train.GetTrainTime(71, AVPtr->ArrivalTime)) //running late, added at v2.16.1
22240  {
22241  StopTimeDouble = double(Train.GetTrainTime(72, (AVPtr + 2)->DepartureTime) - TTClockTime) * 86400.0 / 60.0; //may be -ve but if so it's set to 0.5 later
22242  // can't convert a TDateTime to a float directly
22243  }
22244  }
22245  if(StopTimeDouble < 0.5)
22246  {
22247  StopTimeDouble = 0.5;
22248  }
22249  // at least 30 secs delay at station
22250  LaterStopTime += float(StopTimeDouble);
22251  RecoverableTime += StopTimeDouble - 0.5;
22252  if((LaterStopNumber == 1) && (TrainID > -1))
22253  {
22254  TrainVectorAtIdent(69, TrainID).FirstLaterStopRecoverableTime = RecoverableTime;
22255  }
22256  AVPtr++;
22257  AVPtr++;
22258  AVPtr++;
22259  }
22260  else // does something else at the location so no calculation needed
22261  {
22262  Utilities->CallLogPop(2086);
22263  return(-1);
22264  }
22265  }
22266  }
22267  }
22268  NextElement = Track->TrackElementAt(950, CurrentElement).Conn[CurrentExitPos];
22269  if(NextElement == -1) // reached end element, no action needed
22270  {
22271  Utilities->CallLogPop(2077);
22272  return(-1);
22273  }
22274  NextEntryPos = Track->TrackElementAt(919, CurrentElement).ConnLinkPos[CurrentExitPos];
22275  // get NextExitPos
22276  if(Track->TrackElementAt(920, NextElement).TrackType == Points)
22277  {
22278  if((NextEntryPos == 0) || (NextEntryPos == 2))
22279  // leading entry point
22280  {
22281  if(Track->TrackElementAt(921, NextElement).Attribute == 0)
22282  {
22283  NextExitPos = 1;
22284  }
22285  else
22286  {
22287  NextExitPos = 3;
22288  }
22289  }
22290  else
22291  {
22292  NextExitPos = 0; // trailing entry point
22293  }
22294  }
22295  else
22296  {
22297  NextExitPos = Track->GetNonPointsOppositeLinkPos(NextEntryPos);
22298  }
22299  CurrentElement = NextElement;
22300  CurrentEntryPos = NextEntryPos;
22301  CurrentExitPos = NextExitPos;
22302  CurrentExitConfig = Track->TrackElementAt(922, CurrentElement).Config[CurrentExitPos];
22303  CurrentAttribute = Track->TrackElementAt(923, CurrentElement).Attribute;
22304  CurrentElementFailed = Track->TrackElementAt(1550, CurrentElement).Failed; //added at v2.13.2
22305  }
22306  if((CurrentExitConfig == Signal) && (CurrentAttribute == 0))
22307  // ok if autosig route after red signal, no action needed
22308  {
22309  int NextElement = Track->TrackElementAt(924, CurrentElement).Conn[CurrentExitPos];
22310  int NextEntryPos = Track->TrackElementAt(925, CurrentElement).ConnLinkPos[CurrentExitPos];
22311  int RouteNumber; // holder for referenced value, not used
22312  if((AllRoutes->GetRouteTypeAndNumber(31, NextElement, NextEntryPos, RouteNumber) == TAllRoutes::AutoSigsRoute) && !CurrentElementFailed)
22313  { //CurrentElementFailed added at v2.13.2
22314  Utilities->CallLogPop(2095);
22315  return(-1);
22316  }
22317  }
22318 
22319  if(TrackSpeedCount > 0)
22320  {
22321  MaxAllowableSpeed = CumTrackSpeed / TrackSpeedCount;
22322  }
22323  else // shouldn't reach here but include to prevent divide by zero error
22324  {
22325  if(CurrentEntryPos > 1)
22326  {
22327  MaxAllowableSpeed = Track->TrackElementAt(1433, CurrentElement).SpeedLimit23;
22328  }
22329  else
22330  {
22331  MaxAllowableSpeed = Track->TrackElementAt(1434, CurrentElement).SpeedLimit01;
22332  }
22333  // Train MaxRunningSpeed taken into account in RebuildOpTimeToActMultimap
22334  }
22335 
22336  if(LaterStopNumber > 0)
22337  {
22338  KmPerLocationStop = float(DistanceToRedSignal) / LaterStopNumber / 1000; // m to km
22339  AvTrackSpeed = (8.75 * KmPerLocationStop) + 44;
22340  }
22341  else
22342  // Av speed calculation based on formula: Speed = 8.75*(kms/location stop) + 44 km/sec (from experiments), subject to a maximum of
22343  // average line speed/2 (for half distance accelerating and half decelerating.
22344  {
22345  AvTrackSpeed = (sqrt(float(DistanceToRedSignal) / 1000) * 44) + 60;
22346  // using linear trendline for accel & decel distance at various speeds
22347  // at half braking, speed never < 60 using this
22348  }
22349  if(AvTrackSpeed > MaxAllowableSpeed)
22350  {
22351  AvTrackSpeed = MaxAllowableSpeed;
22352  }
22353  Utilities->CallLogPop(2096);
22354  return(DistanceToRedSignal);
22355 }
22356 
22357 // ---------------------------------------------------------------------------
22358 // end of TTrainController entries
22359 // ---------------------------------------------------------------------------
TTrain::LinkOccupied
bool LinkOccupied(int Caller, int TrackVectorPosition, int LinkNumber)
Added at v1.2.0: true if any part of train on specific link, false otherwise, including no link prese...
Definition: TrainUnit.cpp:9192
TAllRoutes::TrackIsInARoute
bool TrackIsInARoute(int Caller, int TrackVectorPosition, int LinkPos)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19495
TActionVectorEntry::EventTime
TDateTime EventTime
Definition: TrainUnit.h:133
TTrain::AllowedToPassRedSignal
bool AllowedToPassRedSignal
set when train has been called on, or when under signaller control and instructed to pass a red signa...
Definition: TrainUnit.h:385
JoinedByOther
@ JoinedByOther
Definition: TrainUnit.h:51
TTrainController::CheckShuttleRepeatTime
bool CheckShuttleRepeatTime(int Caller, TDateTime ForwardEventTime, TDateTime ReverseEventTime, int RepeatMinutes)
Check that shuttle link services have consistent times, true for success.
Definition: TrainUnit.cpp:16034
TTrain::ZeroPowerNoNewShuttleFromNonRepeatMessage
bool ZeroPowerNoNewShuttleFromNonRepeatMessage
Definition: TrainUnit.h:351
TTrain::ZeroPowerNoNewServiceMessage
bool ZeroPowerNoNewServiceMessage
Definition: TrainUnit.h:350
TTrain::VOffset
int VOffset[4]
each headcode character is an 8x8 pixel graphic and must be placed within a 16x16 pixel element,...
Definition: TrainUnit.h:498
TTrainController
Handles all train and timetable activities, only one object created.
Definition: TrainUnit.h:712
TTrain::TTrain
TTrain(int Caller, int RearStartElementIn, int RearStartExitPosIn, AnsiString InputCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, TTrainMode TrainMode, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerMaxSpeed)
Constructor, sets listed member values.
Definition: TrainUnit.cpp:72
TAllRoutes::LockedRouteVector
TLockedRouteVector LockedRouteVector
the vector that stores all the locked routes on the railway
Definition: TrackUnit.h:1742
TRailGraphics::smOrange
Graphics::TBitmap * smOrange
Definition: GraphicUnit.h:904
TActionVector
std::vector< TActionVectorEntry > TActionVector
contains all actions for a single train
Definition: TrainUnit.h:174
TUtilities::LoadFileString
AnsiString LoadFileString(std::ifstream &InFile)
loads a string value from the file
Definition: Utilities.cpp:190
TTrainController::CheckStartPositionValidity
bool CheckStartPositionValidity(int Caller, AnsiString RearElementStr, AnsiString FrontElementStr, bool GiveMessages)
A timetable validation function where train starting positions are checked for validity,...
Definition: TrainUnit.cpp:15696
TTrainDataEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:209
TTrain::ChangeTrainDirection
void ChangeTrainDirection(int Caller, bool NoLogFlag)
Reverses the direction of motion of the train.
Definition: TrainUnit.cpp:6253
TActionVectorEntry::SplitDistribution
AnsiString SplitDistribution
Definition: TrainUnit.h:121
TTrain::MidEntryPos
int MidEntryPos
Definition: TrainUnit.h:373
TTrainController::RebuildOpTimeToActMultimap
void RebuildOpTimeToActMultimap(int Caller)
new v2.2.0 for OperatorActionPanel (OperatorActionPanel changed for ActionsDueForm at v2....
Definition: TrainUnit.cpp:21669
TTrainController::PwrHigh
bool PwrHigh
Definition: TrainUnit.h:822
TTrainController::BuildContinuationTrainExpectationMultiMap
void BuildContinuationTrainExpectationMultiMap(int Caller)
populate the ContinuationTrainExpectationMultiMap during timetable loading
Definition: TrainUnit.cpp:17155
TFixedTrackPiece::GraphicPtr
Graphics::TBitmap * GraphicPtr
the track bitmap for display on the zoomed-in railway
Definition: TrackUnit.h:92
SignallerMoveForwards
@ SignallerMoveForwards
Definition: TrainUnit.h:52
TRailGraphics::CodeR
Graphics::TBitmap * CodeR
Definition: GraphicUnit.h:1017
Create
@ Create
Definition: TrainUnit.h:51
TTrainController::TContinuationEntryVecPosVector
std::vector< int > TContinuationEntryVecPosVector
ensures only one train displayed for a given continuation
Definition: TrainUnit.h:799
TAllRoutes::SetAllRearwardsSignals
void SetAllRearwardsSignals(int Caller, int Attribute, int RouteNumber, int RouteStartPosition)
Set rearwards signals from the specified route starting position.
Definition: TrackUnit.cpp:20502
Arrive
@ Arrive
Definition: TrainUnit.h:51
ChangeDirection
@ ChangeDirection
Definition: TrainUnit.h:51
TTrain::ZeroPowerNoCDTMessage
bool ZeroPowerNoCDTMessage
Definition: TrainUnit.h:349
TTrain::CallingOnFlag
bool CallingOnFlag
calling on permitted
Definition: TrainUnit.h:391
Depart
@ Depart
Definition: TrainUnit.h:51
TRailGraphics::gl89set
Graphics::TBitmap * gl89set
Definition: GraphicUnit.h:721
TTrainController::CheckHeadCodeValidity
bool CheckHeadCodeValidity(int Caller, bool GiveMessages, AnsiString HeadCode)
Returns true if the headcode complies with requirements.
Definition: TrainUnit.cpp:12302
TRailGraphics::gl88set
Graphics::TBitmap * gl88set
Definition: GraphicUnit.h:719
PerfLogForm
TPerfLogForm * PerfLogForm
Definition: PerfLogUnit.cpp:11
clBufferStopBackground
#define clBufferStopBackground
Definition: GraphicUnit.h:292
TTrain::MaxRunningSpeed
double MaxRunningSpeed
the current maximum train running speed
Definition: TrainUnit.h:435
TTrack::BarriersDownVector
TActiveLCVector BarriersDownVector
vector of LCs with barriers down
Definition: TrackUnit.h:804
TPrefDirElement::GetXLinkPos
int GetXLinkPos() const
Returns the XLink array position.
Definition: TrackUnit.h:287
TTrack::IsLCBarrierDownAtHV
bool IsLCBarrierDownAtHV(int Caller, int HLoc, int VLoc)
True if an open (to trains) level crossing is found at H & V.
Definition: TrackUnit.cpp:7480
RestoreTimetableControl
@ RestoreTimetableControl
Definition: TrainUnit.h:52
FailCrashed
@ FailCrashed
Definition: TrainUnit.h:40
TRailGraphics::TempHeadCode
Graphics::TBitmap * TempHeadCode
Definition: GraphicUnit.h:910
TAllRoutes::AutoSigsRoute
@ AutoSigsRoute
Definition: TrackUnit.h:1671
IntermediateSequence
@ IntermediateSequence
Definition: TrainUnit.h:77
TTrack::TSigElement::Attribute
int Attribute
the signal state - red, yellow, double yellow or green
Definition: TrackUnit.h:730
TActionVectorEntry::LocationType
TTimetableLocationType LocationType
indicates where the train is when the relevant action occurs
Definition: TrainUnit.h:139
TTrack::GapFlashGreenPosition
int GapFlashGreenPosition
Definition: TrackUnit.h:786
TAllRoutes::TRouteElementPair
std::pair< int, unsigned int > TRouteElementPair
defines a specific element in a route, the first (int) value is the vector position in the AllRoutesV...
Definition: TrackUnit.h:1683
NamedNonStationLocation
@ NamedNonStationLocation
Definition: TrackUnit.h:67
FinRemHere
@ FinRemHere
Definition: TrainUnit.h:66
TTrain::HeadCodeGrPtr
Graphics::TBitmap * HeadCodeGrPtr[4]
points to the headcode segment graphics e.g. 5,A,4,7.
Definition: TrainUnit.h:515
TTrainController::TContinuationTrainExpectationEntry::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the service entry in the timetable's TrainDataVector
Definition: TrainUnit.h:760
FSHNewService
@ FSHNewService
Definition: TrainUnit.h:67
TTrain::CoastingBrakeRate
double CoastingBrakeRate
the train brake rate when coasting
Definition: TrainUnit.h:443
TTrain::MaxBrakeRate
double MaxBrakeRate
the maximum brake rate that the train can achieve
Definition: TrainUnit.h:439
TTimetableLocationType
TTimetableLocationType
Definition: TrainUnit.h:71
TAllRoutes::DiagonalFouledByRoute
bool DiagonalFouledByRoute(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
As above but only checks for a route (may or may not be a train present (new at v1....
Definition: TrackUnit.cpp:21263
TTrainController::CheckForDuplicateCrossReferences
bool CheckForDuplicateCrossReferences(int Caller, AnsiString MainHeadCode, AnsiString SecondHeadCode, bool GiveMessages)
A timetable validation function where referenced services are checked for uniqueness,...
Definition: TrainUnit.cpp:14929
TTrackElement::StationEntryStopLinkPos2
int StationEntryStopLinkPos2
Definition: TrackUnit.h:153
TTrain::MidExitPos
int MidExitPos
Definition: TrainUnit.h:373
TRailGraphics::CodeD
Graphics::TBitmap * CodeD
Definition: GraphicUnit.h:1003
TTrain::TrainFailed
bool TrainFailed
Definition: TrainUnit.h:419
TUtilities::CheckFileStringZeroDelimiter
bool CheckFileStringZeroDelimiter(std::ifstream &InFile)
checks that the value is a string ('0' only accepted as the delimiter), returns true for success
Definition: Utilities.cpp:435
TTrack::TTrackVectorIterator
std::vector< TTrackElement >::iterator TTrackVectorIterator
iterator for TTrackVector
Definition: TrackUnit.h:652
TRailGraphics::Code_w
Graphics::TBitmap * Code_w
Definition: GraphicUnit.h:986
FailDerailed
@ FailDerailed
Definition: TrainUnit.h:40
TTrainController::SPADWarning
bool SPADWarning
Definition: TrainUnit.h:808
TTrain::ZeroPowerNoFrontSplitMessage
bool ZeroPowerNoFrontSplitMessage
Definition: TrainUnit.h:345
TRailGraphics::Code_s
Graphics::TBitmap * Code_s
Definition: GraphicUnit.h:982
TTrainController::GetExitLocationAndAt
AnsiString GetExitLocationAndAt(int Caller, TNumList &ExitList, AnsiString &AllowedExits) const
Check all timetable names in ExitList, if all same return " at [name]" + AllowableExits = elements,...
Definition: TrainUnit.cpp:20957
TTrain::SaveOneSessionTrain
void SaveOneSessionTrain(int Caller, std::ofstream &OutFile)
Data for a single train is saved to a session file.
Definition: TrainUnit.cpp:8068
TTrain::DepartureTimeSet
bool DepartureTimeSet
set when stopped at a location and the next action is departure (set in UpdateTrain when ReleaseTime ...
Definition: TrainUnit.h:393
TTrain::Plotted
bool Plotted
set when train plotted on screen
Definition: TrainUnit.h:487
TTrain::LagElement
int LagElement
Definition: TrainUnit.h:373
TTrain::RemainHere
void RemainHere(int Caller)
Sends the 'train terminated' message to the performance log and sets TimetableFinished to true.
Definition: TrainUnit.cpp:6390
TTrackElement::StationEntryStopLinkPos3
int StationEntryStopLinkPos3
Definition: TrackUnit.h:153
TTrainController::BufferAttentionWarning
bool BufferAttentionWarning
Definition: TrainUnit.h:808
TTrainController::TrainVectorAtIdent
TTrain & TrainVectorAtIdent(int Caller, int TrainID)
Return a reference to the train with ID TrainID, carries out validity checking on TrainID.
Definition: TrainUnit.cpp:10477
TPrefDirElement::GetRouteEXGraphicPtr
Graphics::TBitmap * GetRouteEXGraphicPtr()
Returns route graphic.
Definition: TrackUnit.h:323
TRailGraphics::smLightBlue
Graphics::TBitmap * smLightBlue
Definition: GraphicUnit.h:901
TTrainController::TContinuationAutoSigEntry::RouteNumber
int RouteNumber
the AllRoutesVector position of the route
Definition: TrainUnit.h:734
TTrainController::StopTTClockMessage
void StopTTClockMessage(int Caller, AnsiString Message)
sends a message to the user and stops the timetable clock while it is displayed
Definition: TrainUnit.cpp:16886
TExitInfo::TimeToExitSecs
short TimeToExitSecs
Definition: TrainUnit.h:107
TTrainController::OperatingTrainLateArr
int OperatingTrainLateArr
< all these set to 0 in constructor
Definition: TrainUnit.h:866
TRailGraphics::CodeJ
Graphics::TBitmap * CodeJ
Definition: GraphicUnit.h:1009
TUtilities::IncrementAnsiTimeOneMinute
AnsiString IncrementAnsiTimeOneMinute(AnsiString TimeVal)
takes "HH:MM" and increments it to "HH:MX", where MX == MM + 1, incrementing the hour if necessary
Definition: Utilities.cpp:826
TRailGraphics::CodeQ
Graphics::TBitmap * CodeQ
Definition: GraphicUnit.h:1016
TDisplay::GetOutputLog9
TLabel * GetOutputLog9()
Definition: DisplayUnit.h:185
TAllRoutes::RemoveRouteElement
void RemoveRouteElement(int Caller, int HLoc, int VLoc, int ELink)
Erases the route element from Route2MultiMap and from the PrefDirVector.
Definition: TrackUnit.cpp:20291
TTrainController::CrashWarning
bool CrashWarning
Definition: TrainUnit.h:808
TTrainController::CheckLocationValidity
bool CheckLocationValidity(int Caller, AnsiString LocStr, bool GiveMessages, bool CheckLocationsExistInRailway)
Returns true if the location name complies with requirements.
Definition: TrainUnit.cpp:12252
TAllRoutes::TLockedRouteClass::LastXLinkPos
int LastXLinkPos
the XLinkPos value of the last (i.e. most forward) element in the route
Definition: TrackUnit.h:1663
TTrain::PlotTrain
void PlotTrain(int Caller, TDisplay *Disp)
Plots the train on the display in normal (zoomed-in) mode.
Definition: TrainUnit.cpp:9164
TNumListIterator
TNumList::iterator TNumListIterator
Definition: TrainUnit.h:95
FailLevelCrossingCrash
@ FailLevelCrossingCrash
Definition: TrainUnit.h:42
TActionVectorEntry::DepartureTime
TDateTime DepartureTime
relevant times at which the action is timetabled, zeroed on creation so change to -1 as a marker for ...
Definition: TrainUnit.h:133
TRailGraphics::Code_f
Graphics::TBitmap * Code_f
Definition: GraphicUnit.h:969
FailCreateLockedRoute
@ FailCreateLockedRoute
Definition: TrainUnit.h:41
TTrainController::TContinuationTrainExpectationEntry::IncrementalDigits
int IncrementalDigits
Repeat headcode separation.
Definition: TrainUnit.h:756
TTrainController::SaveSessionTrains
void SaveSessionTrains(int Caller, std::ofstream &SessionFile)
save trains to a session file
Definition: TrainUnit.cpp:16901
TOneCompleteFormattedTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:267
TRailGraphics::Code_d
Graphics::TBitmap * Code_d
Definition: GraphicUnit.h:967
TTrain::JoinedBy
void JoinedBy(int Caller)
Carry out the actions needed when a train is waiting to be joined by another train.
Definition: TrainUnit.cpp:6173
FailIncorrectExit
@ FailIncorrectExit
Definition: TrainUnit.h:42
TRailGraphics::Code_t
Graphics::TBitmap * Code_t
Definition: GraphicUnit.h:983
TTrainController::GetServiceFromVector
TTrainDataEntry GetServiceFromVector(AnsiString Caller, AnsiString HeadCode, TTrainDataVector Vector, bool &FinishType, bool &FoundFlag)
Return the TrainDataVector entry corresponding to ServiceReference, FinishType is 0 for end of servic...
Definition: TrainUnit.cpp:20313
TRailGraphics::ChangeBackgroundColour
void ChangeBackgroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour, bool &ColourError)
Definition: GraphicUnit.cpp:3593
TTrain::GetLeadElement
int GetLeadElement()
get LeadElement - used in RouteLockingRequired in TrackUnit.cpp
Definition: TrainUnit.h:694
TimeCmd
@ TimeCmd
Definition: TrainUnit.h:66
TTrain::BackgroundPtr
Graphics::TBitmap * BackgroundPtr[4]
the existing track graphic that the train headcode segment covers up (one for each headcode segment)
Definition: TrainUnit.h:511
TUtilities::LoadFileDouble
double LoadFileDouble(std::ifstream &InFile)
loads a double value from the file (converts from a string to a double) and uses the local decimal po...
Definition: Utilities.cpp:172
TTrain::ZeroPowerDepartMessage
bool ZeroPowerDepartMessage
Definition: TrainUnit.h:354
TTrain::DistanceToStationStop
int DistanceToStationStop
calculated in UpdateTrain & used in CalcDistanceToRedSignalandStopTime to cater for trains running ea...
Definition: TrainUnit.h:469
TTrainController::EntryPos
int EntryPos(int Caller, int TrainIDIn, int TrackVectorNumber)
Return the track entry link (Link[]) array position for the given train on track element at track vec...
Definition: TrainUnit.cpp:10439
SignallerLeave
@ SignallerLeave
Definition: TrainUnit.h:53
TTrain::PlotEntryPos
int PlotEntryPos[4]
the LinkPos value corresponding to the train entry link of the element where each of the 4 headcode c...
Definition: TrainUnit.h:504
NotStarted
@ NotStarted
Definition: TrainUnit.h:88
TTrack::OneNamedLocationElementAtLocation
bool OneNamedLocationElementAtLocation(int Caller, AnsiString LocationName)
True if there is at least one named location element with name 'LocationName', used in timetable inte...
Definition: TrackUnit.cpp:11513
TTrainController::SPADEvents
int SPADEvents
Definition: TrainUnit.h:860
TTrainController::LastTTTime
AnsiString LastTTTime
Stores the last time used in the timetable as an AnsiString - used for timetable analysis.
Definition: TrainUnit.h:802
TTrack::OneStationLongEnoughForSplit
bool OneStationLongEnoughForSplit(int Caller, AnsiString LocationName)
Definition: TrackUnit.cpp:10987
TTrain::ZeroPowerNoRepeatShuttleMessage
bool ZeroPowerNoRepeatShuttleMessage
Definition: TrainUnit.h:352
TTrackElement::SigAspect
enum TTrackElement::@1 SigAspect
TRailGraphics::gl90set
Graphics::TBitmap * gl90set
Definition: GraphicUnit.h:724
TTrain::ActionVectorEntryPtr
TActionVectorEntry * ActionVectorEntryPtr
points to the current position in the ActionVector (a member of the TTrainDataEntry class)
Definition: TrainUnit.h:379
TTrainDataEntry
Contains all data for a single timetable service entry.
Definition: TrainUnit.h:207
LeadMid
@ LeadMid
Definition: TrainUnit.h:300
FailMissedPass
@ FailMissedPass
Definition: TrainUnit.h:41
clSignalStopBackground
#define clSignalStopBackground
Definition: GraphicUnit.h:300
TTrainController::LateDeps
int LateDeps
Definition: TrainUnit.h:850
TAllRoutes::IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber
bool IsElementInLockedRouteGetPrefDirElementGetLockedVectorNumber(int Caller, int TrackVectorPosition, int XLinkPos, TPrefDirElement &PrefDirElement, int &LockedVectorNumber)
Checks whether the preferred direction element at TrackVectorPosition with XLinkPos value is in a loc...
Definition: TrackUnit.cpp:20776
TrackUnit.h
TTrainController::CheckStartAllowable
bool CheckStartAllowable(int Caller, int RearPosition, int RearExitPos, AnsiString HeadCode, bool ReportFlag, TActionEventType &EventType)
Called when trying to introduce a new train - checks for points in correct orientation,...
Definition: TrainUnit.cpp:15780
TTrack::ResetAllTrainIDsAndFailedPointOrigSpeedLimits
void ResetAllTrainIDsAndFailedPointOrigSpeedLimits(int Caller)
Definition: TrackUnit.cpp:7852
TTrainController::RebuildTimeToExitMultiMap
void RebuildTimeToExitMultiMap(int Caller)
new for multiplayer
Definition: TrainUnit.cpp:21840
TRailGraphics::Code_r
Graphics::TBitmap * Code_r
Definition: GraphicUnit.h:981
TTrain::SendMissedActionLogs
void SendMissedActionLogs(int Caller, int IncNum, TActionVectorEntry *Ptr)
Missed actions (see NameInTimetableBeforeCDT above) sent to the performance log and performance file.
Definition: TrainUnit.cpp:6428
TOneRoute::RouteID
int RouteID
the ID number of the route, this is needed for session saves
Definition: TrackUnit.h:1558
TTrainController::TContinuationTrainExpectationEntry::RepeatNumber
int RepeatNumber
service RepeatNumber
Definition: TrainUnit.h:752
TRailGraphics::CodeX
Graphics::TBitmap * CodeX
Definition: GraphicUnit.h:1023
TOneTrainFormattedEntry::Time
AnsiString Time
the time of the action as a string
Definition: TrainUnit.h:254
TRailGraphics::CodeT
Graphics::TBitmap * CodeT
Definition: GraphicUnit.h:1019
TTrainDataEntry::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:217
TTrainController::LastTrainLoaded
int LastTrainLoaded
displays last train loaded from session file, used for debugging
Definition: TrainUnit.h:870
TTrainController::NotStartedTrainLateMins
float NotStartedTrainLateMins
total late minutes of trains that haven't started yet on exit operation for locations not reached yet
Definition: TrainUnit.h:827
Utilities.h
TTrainController::TContinuationAutoSigVectorIterator
TContinuationAutoSigVector::iterator TContinuationAutoSigVectorIterator
Definition: TrainUnit.h:742
TTrain::SkippedDeparture
bool SkippedDeparture
< used for terminating a service early and becoming new follow-on service
Definition: TrainUnit.h:333
TTrain::TrainOnContinuation
bool TrainOnContinuation(int Caller)
Returns true if any part of train on a continuation - called when checking for failures,...
Definition: TrainUnit.cpp:9641
ChangeDescription
@ ChangeDescription
Definition: TrainUnit.h:54
TTrainController::CheckSessionLockedRoutes
bool CheckSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for locked routes, true for success.
Definition: TrainUnit.cpp:17004
FailTrainEntry
@ FailTrainEntry
Definition: TrainUnit.h:39
MidLag
@ MidLag
Definition: TrainUnit.h:300
TDisplay::Update
void Update()
Repaint the screen display.
Definition: DisplayUnit.h:222
TTrainController::LocServiceTimesDepTimeSort
bool LocServiceTimesDepTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:907
TTrainController::CheckSessionTrains
bool CheckSessionTrains(int Caller, std::ifstream &InFile)
Part of the session file integrity check for train entries, true for success.
Definition: TrainUnit.cpp:16942
StartNew
@ StartNew
Definition: TrainUnit.h:66
TakeSignallerControl
@ TakeSignallerControl
Definition: TrainUnit.h:51
LeadMidLag
@ LeadMidLag
Definition: TrainUnit.h:300
TTrack::GapFlashGreen
TGraphicElement * GapFlashGreen
Definition: TrackUnit.h:808
TTrain::ZeroPowerNoRepeatShuttleOrNewServiceMessage
bool ZeroPowerNoRepeatShuttleOrNewServiceMessage
Definition: TrainUnit.h:353
TrainFailure
@ TrainFailure
Definition: TrainUnit.h:52
TTrain::MinsDelayed
float MinsDelayed
new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain
Definition: TrainUnit.h:457
TTrack::TSigElement::SigPtr
Graphics::TBitmap * SigPtr
pointer to the graphic
Definition: TrackUnit.h:732
TTrainController::TimeToExitMultiMap
TTimeToExitMultiMap TimeToExitMultiMap
Map of times to exit & exit coordinates.
Definition: TrainUnit.h:1013
TAllRoutes::FindRouteNumberFromRoute2MultiMapNoErrors
bool FindRouteNumberFromRoute2MultiMapNoErrors(int Caller, int HLoc, int VLoc, int ELink, int &RouteNumber)
If a route is present at H, V & Elink returns true with RouteNumber giving vector position in AllRout...
Definition: TrackUnit.cpp:20039
TTrainController::Last2CharactersBothDigits
bool Last2CharactersBothDigits(int Caller, AnsiString HeadCode)
Checks the last two characters in HeadCode and returns true if both are digits.
Definition: TrainUnit.cpp:11776
TRailGraphics::Code2
Graphics::TBitmap * Code2
Definition: GraphicUnit.h:992
TTrack::GetVectorPositionsFromInactiveTrackMap
TIMPair GetVectorPositionsFromInactiveTrackMap(int Caller, int HLoc, int VLoc, bool &FoundFlag)
Similar to GetVectorPositionFromTrackMap but for inactive elements, a pair is returned because there ...
Definition: TrackUnit.cpp:5960
TTrain::BackgroundColour
TColor BackgroundColour
the background colour of the train's headcode graphics
Definition: TrainUnit.h:518
TTrainOperatingData::EventReported
TActionEventType EventReported
Definition: TrainUnit.h:186
TTrain
Definition: TrainUnit.h:306
TRailGraphics::Code_z
Graphics::TBitmap * Code_z
Definition: GraphicUnit.h:989
TTrainController::CheckTimeValidity
bool CheckTimeValidity(int Caller, AnsiString TimeStr, TDateTime &Time)
returns true if the time complies with requirements
Definition: TrainUnit.cpp:11795
clSignallerStopped
#define clSignallerStopped
Definition: GraphicUnit.h:299
TOnePrefDir::GetFixedPrefDirElementAt
const TPrefDirElement & GetFixedPrefDirElementAt(int Caller, int At) const
Return a non-modifiable element at PrefDirVector position 'At'.
Definition: TrackUnit.cpp:12249
FailBuffersPreventingStart
@ FailBuffersPreventingStart
Definition: TrainUnit.h:42
TTrainController::LocServiceTimesArrTimeSort
bool LocServiceTimesArrTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:902
TOneTrainFormattedEntry::Action
AnsiString Action
includes location if relevant
Definition: TrainUnit.h:252
TTrainController::ContinuationTrainExpectationMultiMap
TContinuationTrainExpectationMultiMap ContinuationTrainExpectationMultiMap
Multimap for TContinuationTrainExpectationEntry objects, the access key is the expectation time.
Definition: TrainUnit.h:884
TPerfLogForm::PerformanceLog
void PerformanceLog(int Caller, AnsiString Statement)
Send Statement to the performance log on screen and to the file.
Definition: PerfLogUnit.cpp:32
TTrain::StoppedForTrainInFront
bool StoppedForTrainInFront
Definition: TrainUnit.h:493
GapJump
@ GapJump
Definition: TrackUnit.h:66
TRailGraphics::CodeK
Graphics::TBitmap * CodeK
Definition: GraphicUnit.h:1010
TTrainController::MissedStops
int MissedStops
Definition: TrainUnit.h:853
NoSequence
@ NoSequence
Definition: TrainUnit.h:77
clCallOnBackground
#define clCallOnBackground
Definition: GraphicUnit.h:293
TTrackElement::Length01
int Length01
Definition: TrackUnit.h:151
TTrackElement::SpeedLimit01
int SpeedLimit01
Definition: TrackUnit.h:151
TTrain::TerminatedMessageSent
bool TerminatedMessageSent
set when a 'train terminated' message has been logged, to prevent its being logged more than once
Definition: TrainUnit.h:415
TTrain::FinishJoin
void FinishJoin(int Caller)
Carry out the actions needed when a train is waiting to join another train.
Definition: TrainUnit.cpp:6124
TTrainController::FinishedOperation
void FinishedOperation(int Caller)
called when exiting operation mode to delete all trains and timetable data etc
Definition: TrainUnit.cpp:10070
TTrain::MidElement
int MidElement
Definition: TrainUnit.h:373
TAllRoutes::CallonVector
std::vector< TCallonEntry > CallonVector
the store of all call-on entries
Definition: TrackUnit.h:1710
TTrain::LeadElement
int LeadElement
Definition: TrainUnit.h:373
clDerailedBackground
#define clDerailedBackground
Definition: GraphicUnit.h:295
TTrain::RepeatNumber
int RepeatNumber
indicates which of the repeating services this train represents (0 = first service)
Definition: TrainUnit.h:367
TTrainDataEntry::ActionVector
TActionVector ActionVector
all the actions for the train
Definition: TrainUnit.h:227
TTrainController::BaseTime
TDateTime BaseTime
CurrentDateTime (i.e. real time) when operation restarts after a pause.
Definition: TrainUnit.h:715
TTrain::StartSpeed
int StartSpeed
the speed of the train when introduced into the railway (in km/h)
Definition: TrainUnit.h:371
TExitInfo::TExitInfo
TExitInfo()
Definition: TrainUnit.cpp:63
TTrainController::LoadSessionLockedRoutes
void LoadSessionLockedRoutes(int Caller, std::ifstream &SessionFile)
load locked routes from a session file
Definition: TrainUnit.cpp:16983
TTrainController::EarlyPasses
int EarlyPasses
Definition: TrainUnit.h:846
TTrain::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:329
TDisplay::GetOutputLog7
TLabel * GetOutputLog7()
Definition: DisplayUnit.h:175
TTrain::TrainInFront
bool TrainInFront
Definition: TrainUnit.h:494
TTrainController::TOpTimeToActMultiMapEntry
std::pair< float, THCandTrainPosParam > TOpTimeToActMultiMapEntry
Definition: TrainUnit.h:797
FailMissedSplit
@ FailMissedSplit
Definition: TrainUnit.h:40
TTrain::EntryTime
TDateTime EntryTime
Definition: TrainUnit.h:473
TExitInfo::RepeatNumber
short RepeatNumber
Definition: TrainUnit.h:106
TTrainController::TContinuationAutoSigEntry::FirstDelay
double FirstDelay
Definition: TrainUnit.h:730
TTrain::LastSigPassedFailed
bool LastSigPassedFailed
flag used to erase route elements in an autosigs route after a failed signal
Definition: TrainUnit.h:401
TTrainController::SaveTrainDataVectorToFile
void SaveTrainDataVectorToFile(int Caller)
diagnostic function to store all train data to a file for examination, not used normally
Definition: TrainUnit.cpp:16706
TOnePrefDir::PrefDirSize
unsigned int PrefDirSize() const
Return the vector size.
Definition: TrackUnit.h:1416
TTrack::PlotSignal
void PlotSignal(int Caller, TTrackElement TrackElement, TDisplay *Disp)
Plot signals on screen according to their aspect (Attribute value)
Definition: TrackUnit.cpp:6248
End
@ End
Definition: TrackUnit.h:76
TUtilities::FixedMinRepairTime
int FixedMinRepairTime
Definition: Utilities.h:72
TTrain::OneLengthAccelDecel
bool OneLengthAccelDecel
set when a train can only move forwards one element before stopping but needs to accelerate for the f...
Definition: TrainUnit.h:405
TTrain::FloatingLabelNextString
AnsiString FloatingLabelNextString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the 'Next' action.
Definition: TrainUnit.cpp:7072
TTrack::TimetabledLocationNameAllocated
bool TimetabledLocationNameAllocated(int Caller, AnsiString LocationName)
True if a non-empty LocationName found as a timetabled location name i.e. not as a continuation name.
Definition: TrackUnit.cpp:9054
TTrain::FailedTrainNoFinishJoinMessage
bool FailedTrainNoFinishJoinMessage
Definition: TrainUnit.h:347
TTrack::InactiveTrackElementAt
TTrackElement & InactiveTrackElementAt(int Caller, int At)
A range-checked version of InactiveTrackVector.at(At)
Definition: TrackUnit.cpp:10952
ExitRailway
@ ExitRailway
Definition: TrainUnit.h:67
TPrefDirElement::GetTrackVectorPosition
unsigned int GetTrackVectorPosition() const
Returns TrackVectorPosition.
Definition: TrackUnit.h:305
TTrain::StationStopCalculated
bool StationStopCalculated
used in calculating DistanceToStationStop for trains running early before they have reached the stop ...
Definition: TrainUnit.h:411
TTrainController::LoadSessionContinuationAutoSigEntries
void LoadSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
load ContinuationAutoSigEntries from a session file
Definition: TrainUnit.cpp:17066
FailMissedDSC
@ FailMissedDSC
Definition: TrainUnit.h:40
TTrackElement
Basic track elements as implemented in the overall railway layout.
Definition: TrackUnit.h:125
TRailGraphics::Code_n
Graphics::TBitmap * Code_n
Definition: GraphicUnit.h:977
FailMissedNewService
@ FailMissedNewService
Definition: TrainUnit.h:41
TUtilities::Format96HHMMSS
AnsiString Format96HHMMSS(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm:ss where hh runs from 00 to 95 & resets when...
Definition: Utilities.cpp:788
TRailGraphics::smSolidBgnd
Graphics::TBitmap * smSolidBgnd
Definition: GraphicUnit.h:1027
clTRSBackground
#define clTRSBackground
Definition: GraphicUnit.h:304
TTrainController::OtherMissedEvents
int OtherMissedEvents
Definition: TrainUnit.h:858
TTrainController::EarlyExits
int EarlyExits
Definition: TrainUnit.h:847
TTrain::ExitSpeedHalf
double ExitSpeedHalf
speed when half way into the next element
Definition: TrainUnit.h:429
SignalPost
@ SignalPost
Definition: TrackUnit.h:66
TRailGraphics::Code_h
Graphics::TBitmap * Code_h
Definition: GraphicUnit.h:971
TTrain::LastActionTime
TDateTime LastActionTime
time of the last timetabled event, used to ensure at least a 30 second delay before the next action
Definition: TrainUnit.h:477
TDisplay::GetOutputLog1
TLabel * GetOutputLog1()
Return pointers to warning message logs (appear above the railway display during operation)
Definition: DisplayUnit.h:144
TAllRoutes::TRoute2MultiMapIterator
TRoute2MultiMap::iterator TRoute2MultiMapIterator
Definition: TrackUnit.h:1687
TTrain::TrainFailurePending
bool TrainFailurePending
set when failure due & takes effect when all PlotElements properly set, added at v2....
Definition: TrainUnit.h:357
FailMissedTerminate
@ FailMissedTerminate
Definition: TrainUnit.h:41
TTrainController::SecondPassActions
bool SecondPassActions(int Caller, bool GiveMessages, bool &TwoLocationFlag)
Carry out further detailed timetable consistency checks, return true for success.
Definition: TrainUnit.cpp:12945
TRailGraphics::Code9
Graphics::TBitmap * Code9
Definition: GraphicUnit.h:999
TTrainDataEntry::HeadCode
AnsiString HeadCode
Definition: TrainUnit.h:209
FailTrainInFront
@ FailTrainInFront
Definition: TrainUnit.h:43
TTrain::TrainHasFailed
void TrainHasFailed(int Caller)
Called when there is a random train failure.
Definition: TrainUnit.cpp:5752
TUtilities::MinorDelayFactor
float MinorDelayFactor
Definition: Utilities.h:53
clNormalBackground
#define clNormalBackground
Definition: GraphicUnit.h:298
TTrainController::CheckNonRepeatingShuttleLinksAndSetData
bool CheckNonRepeatingShuttleLinksAndSetData(int Caller, AnsiString MainHeadCode, AnsiString NonRepeatingHeadCode, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where cross references are checked for validity for non-repeating shu...
Definition: TrainUnit.cpp:16057
TTrainController::CheckFourthValidityForSplit
bool CheckFourthValidityForSplit(AnsiString SplitDistributionString, bool GiveMessages)
Checks fourth segment in timetable for train splits - percentage mass then '-' then percentage power ...
Definition: TrainUnit.cpp:12207
TActionVectorEntry::NonRepeatingShuttleLinkEntryPtr
TTrainDataEntry * NonRepeatingShuttleLinkEntryPtr
pointer used by shuttles for the non-shuttle train links, in & out, the corresponding non-shuttle lin...
Definition: TrainUnit.h:147
TTrainController::SPADRisks
int SPADRisks
Definition: TrainUnit.h:861
TTrainController::TLocServiceTimesVector
std::vector< TLocServiceTimes > TLocServiceTimesVector
Definition: TrainUnit.h:783
TTrainController::LocServiceTimesAtLocTimeSort
bool LocServiceTimesAtLocTimeSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:911
TTrain::EntrySpeed
double EntrySpeed
speed at which the train enters the next element
Definition: TrainUnit.h:427
TConfiguration
TConfiguration
< describes the type of track link. 'End' is used for both buffer stop and continuation entry/exit po...
Definition: TrackUnit.h:75
TTrain::TrainInFrontMessage
bool TrainInFrontMessage
flags to indicate whether the respective message has been sent
Definition: TrainUnit.h:355
TTrain::TrainGone
bool TrainGone
set when train has left the railway, so it can be removed from the display at the next clock tick
Definition: TrainUnit.h:489
TTrackType
TTrackType
< describes the type of track element
Definition: TrackUnit.h:65
TTrainController::OnTimePasses
int OnTimePasses
Definition: TrainUnit.h:856
TTrainController::SigSHigh
bool SigSHigh
Definition: TrainUnit.h:822
TRailGraphics::smCaramel
Graphics::TBitmap * smCaramel
Definition: GraphicUnit.h:898
TGraphicElement::PlotOriginal
void PlotOriginal(int Caller, TDisplay *Disp)
Plot the original graphic on screen.
Definition: TrackUnit.cpp:1927
TExitInfo
Definition: TrainUnit.h:103
clBufferAttentionNeeded
#define clBufferAttentionNeeded
Definition: GraphicUnit.h:291
TUtilities::ModerateDelayCutoff
float ModerateDelayCutoff
Definition: Utilities.h:51
TTrain::StoppedAfterSPAD
bool StoppedAfterSPAD
Definition: TrainUnit.h:493
FailSPAD
@ FailSPAD
Definition: TrainUnit.h:39
TTrainController::OnTimeArrivals
int OnTimeArrivals
Definition: TrainUnit.h:854
clTrainFailedBackground
#define clTrainFailedBackground
Definition: GraphicUnit.h:305
TRailGraphics::Code_x
Graphics::TBitmap * Code_x
Definition: GraphicUnit.h:987
clFrontCodeSignaller
#define clFrontCodeSignaller
Definition: GraphicUnit.h:296
TimeCmdHeadCode
@ TimeCmdHeadCode
Definition: TrainUnit.h:66
Utilities
TUtilities * Utilities
Definition: Utilities.cpp:47
TFixedTrackPiece::SmallGraphicPtr
Graphics::TBitmap * SmallGraphicPtr
the track bitmap for display on the zoomed-out railway
Definition: TrackUnit.h:94
TActionVectorEntry::ArrivalTime
TDateTime ArrivalTime
Definition: TrainUnit.h:133
TTrain::StoppedAtBuffers
bool StoppedAtBuffers
Definition: TrainUnit.h:493
TTrainController::CreateTTAnalysisFile
bool CreateTTAnalysisFile(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir, bool ArrChecked, bool DepChecked, bool AtLocChecked, bool DirChecked, int ArrRange, int DepRange)
Generate a timetable analysis file in the 'Formatted Timetables' folder, return false if failed for a...
Definition: TrainUnit.cpp:17882
TRailGraphics::Code_b
Graphics::TBitmap * Code_b
Definition: GraphicUnit.h:965
TTrain::Mass
int Mass
in kg
Definition: TrainUnit.h:467
TRailGraphics::Code0
Graphics::TBitmap * Code0
Definition: GraphicUnit.h:990
TTrainController::ProcessOneTimetableLine
bool ProcessOneTimetableLine(int Caller, int Count, AnsiString OneLine, bool &EndOfFile, bool FinalCall, bool GiveMessages, bool CheckLocationsExistInRailway)
Carry out preliminary (mainly syntax) validity checks on a single timetable service entry and (if Fin...
Definition: TrainUnit.cpp:11039
TTrainController::TContinuationTrainExpectationEntry::IncrementalMinutes
int IncrementalMinutes
Repeat separation in minutes.
Definition: TrainUnit.h:754
TTrain::LeadExitPos
int LeadExitPos
Definition: TrainUnit.h:373
TTrain::FrontElementLength
int FrontElementLength
values associated with the element immediately in front of the train (speed in km/h,...
Definition: TrainUnit.h:465
TTimetableShuttleLinkType
TTimetableShuttleLinkType
Definition: TrainUnit.h:81
TRailGraphics::CodeP
Graphics::TBitmap * CodeP
Definition: GraphicUnit.h:1015
Pass
@ Pass
Definition: TrainUnit.h:53
TTrain::SkipPtrValue
int SkipPtrValue
stores the pointer increment from first action in ActionVector for skipped actions when a departure i...
Definition: TrainUnit.h:381
TNumList
std::list< int > TNumList
a list of valid train exit TrackVector positions for 'Fer' entries
Definition: TrainUnit.h:91
TTrainController::DerailWarning
bool DerailWarning
Definition: TrainUnit.h:808
TTrainController::TContinuationTrainExpectationMultiMapPair
std::pair< TDateTime, TContinuationTrainExpectationEntry > TContinuationTrainExpectationMultiMapPair
a single multimap entry
Definition: TrainUnit.h:768
RouteForceCancelled
@ RouteForceCancelled
Definition: TrainUnit.h:43
EnRoute
@ EnRoute
Definition: TrainUnit.h:72
TTrain::IsTrainIDOnBridgeTrackPos23
bool IsTrainIDOnBridgeTrackPos23(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 2 & 3.
Definition: TrainUnit.cpp:3284
TTrack::DiagonalFouledByTrain
bool DiagonalFouledByTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber, int &TrainID)
As DiagonalFouledByRouteOrTrain (in TAllRoutes) but only checks for a train (may or may not be a rout...
Definition: TrackUnit.cpp:11961
TTrainController::TotLateDepMins
float TotLateDepMins
Definition: TrainUnit.h:838
TDisplay::ZoomOutFlag
bool ZoomOutFlag
true when zoomed-out
Definition: DisplayUnit.h:70
TRailGraphics::CodeC
Graphics::TBitmap * CodeC
Definition: GraphicUnit.h:1002
TTrain::PlotElement
int PlotElement[4]
the TrackVectorPosition of the element where each of the 4 headcode characters is plotted (need to be...
Definition: TrainUnit.h:502
TTrainController::TServiceCallingLocsList
std::list< AnsiString > TServiceCallingLocsList
Used in determining train directions in timetable conflict analysis.
Definition: TrainUnit.h:786
TTrainController::TrainVector
TTrainVector TrainVector
vector containing all trains currently in the railway
Definition: TrainUnit.h:892
TUtilities::ModerateDelayFactor
float ModerateDelayFactor
Definition: Utilities.h:54
TUtilities::MajorDelayCutoff
float MajorDelayCutoff
Definition: Utilities.h:52
TRailGraphics::Code_l
Graphics::TBitmap * Code_l
Definition: GraphicUnit.h:975
TTrainController::TContinuationAutoSigEntry
< TTClockTime when last session saved - to prevent display of warning message on exit session if < 5 ...
Definition: TrainUnit.h:728
TUtilities::CallLogPop
void CallLogPop(int Caller)
pops the last entry off the call stack, throws an error if called when empty
Definition: Utilities.cpp:50
TTrain::IncrementalMinutes
int IncrementalMinutes
the number of minutes to increment by in repeat entries
Definition: TrainUnit.h:361
TTrain::MaximumSpeedLimit
static const int MaximumSpeedLimit
Definition: TrainUnit.h:321
TRailGraphics::CodeI
Graphics::TBitmap * CodeI
Definition: GraphicUnit.h:1008
TTrainController::TotEarlyArrMins
float TotEarlyArrMins
values for performance file summary
Definition: TrainUnit.h:833
TTrain::WriteTrainToImage
void WriteTrainToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TTrainController::WriteTrainsToImage (called by TInterface::SaveOperatingImage1Click) to ad...
Definition: TrainUnit.cpp:9176
TRailGraphics::CodeL
Graphics::TBitmap * CodeL
Definition: GraphicUnit.h:1011
TTrainDataEntry::MaxBrakeRate
double MaxBrakeRate
< true if a description is given for the train, if only headcode given for a follow-on service then f...
Definition: TrainUnit.h:213
FNSNonRepeatToShuttle
@ FNSNonRepeatToShuttle
Definition: TrainUnit.h:66
TDisplay::PlotOutput
void PlotOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot the graphic at screen position HPos & VPos.
Definition: DisplayUnit.cpp:86
TUtilities::SaveFileBool
void SaveFileBool(std::ofstream &OutFile, bool SaveBool)
stores '1' if the bool is true or '0' if false to the file, then a CR
Definition: Utilities.cpp:108
TTrainDataEntry::SignallerSpeed
int SignallerSpeed
in km/h for use when under signaller control
Definition: TrainUnit.h:223
TTrain::AbleToMove
bool AbleToMove(int Caller)
Indicates that a train is not prevented from moving - used to allow appropriate popup menu options wh...
Definition: TrainUnit.cpp:6921
TTrainController::MTBFHours
double MTBFHours
Mean time between train failures in timetable clock hours.
Definition: TrainUnit.h:825
TTrack::TInfrastructureFailureEntry::TVPos
int TVPos
Definition: TrackUnit.h:715
TOneRoute::ForceCancelRoute
void ForceCancelRoute(int Caller)
Cancel a route immediately if a train occupies it when travelling in the wrong direction (or occupies...
Definition: TrackUnit.cpp:19076
TTrain::TrainMode
TTrainMode TrainMode
mode of operation - either Timetable (running under timetable control) or Signaller (running under si...
Definition: TrainUnit.h:481
TTrainController::CheckNonRepeatingShuttleLinkTime
bool CheckNonRepeatingShuttleLinkTime(int Caller, TDateTime ReverseEventTime, TDateTime ForwardEventTime, int RepeatMins, int RepeatNumber)
The forward train is the finish shuttle entry 'Fns-sh', the reverse (new non-repeating service) time ...
Definition: TrainUnit.cpp:16326
FailNoPowerUnableToDepart
@ FailNoPowerUnableToDepart
Definition: TrainUnit.h:43
TUtilities::Format96HHMM
AnsiString Format96HHMM(TDateTime DateTime)
formats a TDateTime into an AnsiString of the form hh:mm where hh runs from 00 to 95 & resets when it...
Definition: Utilities.cpp:807
TTrain::CallOnMaxSpeed
static const int CallOnMaxSpeed
km/h
Definition: TrainUnit.h:313
TTrain::CheckAndCancelRouteForWrongEndEntry
void CheckAndCancelRouteForWrongEndEntry(int Caller, int Element, int EntryPos)
Checks whether Element and EntryPos (where train is about to enter) is on an existing route (or cross...
Definition: TrainUnit.cpp:3492
TRailGraphics::CodeW
Graphics::TBitmap * CodeW
Definition: GraphicUnit.h:1022
TTrain::ReleaseTime
TDateTime ReleaseTime
Definition: TrainUnit.h:475
TTrain::GetTrainHeadCode
AnsiString GetTrainHeadCode(int Caller)
Returns the train headcode, taking account of the RepeatNumber.
Definition: TrainUnit.cpp:5300
TTrain::LowEntryValue
bool LowEntryValue(int EntryLink) const
Returns true if EntryLink is 1, 2, 4 or 7, in these circumstances the front of the train (i....
Definition: TrainUnit.cpp:2805
RearSplit
@ RearSplit
Definition: TrainUnit.h:51
TActionVectorEntry::FrontStartOrRepeatDigits
int FrontStartOrRepeatDigits
dual-purpose variables used for the TrackVectorPositions of the rear and front train starting element...
Definition: TrainUnit.h:131
SignallerControlStop
@ SignallerControlStop
Definition: TrainUnit.h:53
TTrack::TrackVector
TTrackVector TrackVector
Definition: TrackUnit.h:828
TTrain::HasTrainGone
bool HasTrainGone()
Check whether the train has left the railway, so that it can be removed from the display at the next ...
Definition: TrainUnit.h:678
TRailGraphics::Code_i
Graphics::TBitmap * Code_i
Definition: GraphicUnit.h:972
TTrainController::ExcessLCDownMins
float ExcessLCDownMins
total excess time in minutes over the 3 minutes barriers down allowance for level crossings
Definition: TrainUnit.h:831
TRailGraphics::Code5
Graphics::TBitmap * Code5
Definition: GraphicUnit.h:995
TTrackElement::CallingOnSet
bool CallingOnSet
Used for for signals only when a train is being called on - used to plot the position lights.
Definition: TrackUnit.h:135
TUtilities::SaveFileInt
void SaveFileInt(std::ofstream &OutFile, int SaveInt)
stores the int value to the file, then a CR
Definition: Utilities.cpp:121
TrainController
TTrainController * TrainController
the object pointer, one object only - created in InterfaceUnit
Definition: TrainUnit.cpp:55
TUtilities::FailureMode
TFailureMode FailureMode
specifies whether no failures or minor, moderate or major random failures are to be applied (added at...
Definition: Utilities.h:119
TTrain::BufferAtExit
bool BufferAtExit(int Caller, int Element, int Exitpos) const
True if Element is a buffer and Exitpos is the buffer end.
Definition: TrainUnit.cpp:3216
TTrain::IsTrainTerminating
bool IsTrainTerminating(int Caller)
True if train service terminates at its current location.
Definition: TrainUnit.cpp:6893
TTrainController::SecondPassMessage
void SecondPassMessage(bool GiveMessages, AnsiString Message)
Give a user message during timetable integrity checking if GiveMessages is true, ignore if false.
Definition: TrainUnit.cpp:16424
SignallerStepForward
@ SignallerStepForward
Definition: TrainUnit.h:53
TTrack::GetHLocMin
int GetHLocMin()
Definition: TrackUnit.h:894
TDisplay::GetOutputLog5
TLabel * GetOutputLog5()
Definition: DisplayUnit.h:165
TTrainController::LateExits
int LateExits
Definition: TrainUnit.h:852
TTrainController::OpTimeToActUpdateCounter
unsigned int OpTimeToActUpdateCounter
<List of all ServiceRefs that have two or more same locations without a cdt between - loaded during S...
Definition: TrainUnit.h:876
TTrain::BeingCalledOn
bool BeingCalledOn
in course of being called on to a station
Definition: TrainUnit.h:387
TUtilities::CheckFileInt
bool CheckFileInt(std::ifstream &InFile, int Lowest, int Highest)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success
Definition: Utilities.cpp:238
TRailGraphics::ChangeForegroundColour2
void ChangeForegroundColour2(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
New function to do the same as the above but with fewer pixel changes - for use in LoadSession to avo...
Definition: GraphicUnit.cpp:3543
TRailGraphics::Code_e
Graphics::TBitmap * Code_e
Definition: GraphicUnit.h:968
TTrain::CalcTimeToAct
float CalcTimeToAct(int Caller, float &TimeToExit, THVShortPair &ExitPair)
new v2.2.0 for operator action panel. Calculates the time left for operator action to avoid unnecessa...
Definition: TrainUnit.cpp:9240
TUtilities::SignalChangeEventsPerFailure
int SignalChangeEventsPerFailure
number of signal changes between failures - reciprocal of failure probability per change
Definition: Utilities.h:95
TTrain::HoldAtLocationInTTMode
bool HoldAtLocationInTTMode
true if actions are needed before train departs
Definition: TrainUnit.h:337
TTrack::TInfrastructureFailureEntry
Definition: TrackUnit.h:714
TTrain::PlotTrainInZoomOutMode
void PlotTrainInZoomOutMode(int Caller, bool Flash)
Plots the train on screen in zoomed-out mode, state of 'Flash' determines whether the flashing trains...
Definition: TrainUnit.cpp:8986
TTrain::LoadOneSessionTrain
void LoadOneSessionTrain(int Caller, std::ifstream &InFile)
Create one train with relevant member values from the sesion file.
Definition: TrainUnit.cpp:8265
TTrain::PlotBackgroundGraphic
void PlotBackgroundGraphic(int Caller, int ArrayNumber, TDisplay *Disp) const
Replot the graphic pointed to by BackgroundPtr (see above) after a train has passed.
Definition: TrainUnit.cpp:3208
TTrainController::TotLateArrMins
float TotLateArrMins
Definition: TrainUnit.h:837
TTrain::HOffset
int HOffset[4]
Definition: TrainUnit.h:498
FailMissedArrival
@ FailMissedArrival
Definition: TrainUnit.h:40
TTrainController::TrainDataVectorCopy
TTrainDataVector TrainDataVectorCopy
vector containing the internal timetable, the copy is used for conflict analysis only
Definition: TrainUnit.h:890
TTrain::UpdateCounter
unsigned int UpdateCounter
used in train splitting operations to prevent too frequent checks for a location being long enough fo...
Definition: TrainUnit.h:471
NewService
@ NewService
Definition: TrainUnit.h:51
FailEnterLockedRoute
@ FailEnterLockedRoute
Definition: TrainUnit.h:41
TTrainController::PlotAllTrainsInZoomOutMode
void PlotAllTrainsInZoomOutMode(int Caller, bool Flash)
Plots all trains on screen in zoomed-out mode, state of 'Flash' determines whether the flashing train...
Definition: TrainUnit.cpp:17219
TOneRoute
A descendent of TOnePrefDir used for routes. Used during contruction of a route (ConstructRoute) and ...
Definition: TrackUnit.h:1518
TTrain::SignallerStopped
bool SignallerStopped
Definition: TrainUnit.h:493
TTrain::BufferZoomOutFlashRequired
bool BufferZoomOutFlashRequired
set when train is at buffers and is to flash in zoomout mode (i.e. when reaches buffers unexpectedly ...
Definition: TrainUnit.h:389
TAllRoutes::TRouteType
TRouteType
Definition: TrackUnit.h:1670
TTrain::TrainDataEntryPtr
TTrainDataEntry * TrainDataEntryPtr
points to the current position in the timetable's TrainDataVector
Definition: TrainUnit.h:377
TTrainDataEntry::StartSpeed
int StartSpeed
in km/h
Definition: TrainUnit.h:225
TTrainDataEntry::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:221
TTrainController::Derailments
int Derailments
Definition: TrainUnit.h:844
clStationStopBackground
#define clStationStopBackground
Definition: GraphicUnit.h:302
TUtilities::CumulativeDelayedRandMinsAllTrains
int CumulativeDelayedRandMinsAllTrains
the running total of all random delays including knock-on delays for all trains, used to reduce total...
Definition: Utilities.h:101
TDisplay::GetOutputLog6
TLabel * GetOutputLog6()
Definition: DisplayUnit.h:170
ShuttleLink
@ ShuttleLink
Definition: TrainUnit.h:82
TRailGraphics::CodeM
Graphics::TBitmap * CodeM
Definition: GraphicUnit.h:1012
Crossover
@ Crossover
Definition: TrackUnit.h:66
TTrain::ClearToNextSignal
bool ClearToNextSignal(int Caller)
Checks forward from train LeadElement, following leading point attributes but ignoring trailing point...
Definition: TrainUnit.cpp:4900
AtLocation
@ AtLocation
Definition: TrainUnit.h:72
Signal
@ Signal
Definition: TrackUnit.h:76
TRailGraphics::Code_k
Graphics::TBitmap * Code_k
Definition: GraphicUnit.h:974
TRailGraphics::CodeF
Graphics::TBitmap * CodeF
Definition: GraphicUnit.h:1005
TTrack::ContinuationNameMap
std::map< AnsiString, char > ContinuationNameMap
map of all continuation names, char is a dummy
Definition: TrackUnit.h:796
TAllRoutes::GetRouteTypeAndNumber
TRouteType GetRouteTypeAndNumber(int Caller, int TrackVectorPosition, int LinkPos, int &RouteNumber)
Examines Route2MultiMap and if the element at TrackVectorPosition with LinkPos (can be entry or exit)...
Definition: TrackUnit.cpp:19732
TTrainController::TrainDataVector
TTrainDataVector TrainDataVector
Definition: TrainUnit.h:890
TTrainController::ConsolidateSARNTAtLoc
AnsiString ConsolidateSARNTAtLoc(int Caller, const AnsiString Input, int &NumTrainsAtLoc)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for AtLoc listing ...
Definition: TrainUnit.cpp:20592
TAllRoutes::NotAutoSigsRoute
@ NotAutoSigsRoute
Definition: TrackUnit.h:1671
TTrain::ExitTimeHalf
TDateTime ExitTimeHalf
Definition: TrainUnit.h:473
Exited
@ Exited
Definition: TrainUnit.h:88
TTrack::TrackElementAt
TTrackElement & TrackElementAt(int Caller, int At)
A range-checked version of TrackVector.at(At)
Definition: TrackUnit.cpp:10938
TTrain::ActionsSkippedFlag
bool ActionsSkippedFlag
prevents any further skipping until after the next departure
Definition: TrainUnit.h:335
TTrainController::LoadSessionTrains
void LoadSessionTrains(int Caller, std::ifstream &SessionFile)
load trains from a session file
Definition: TrainUnit.cpp:16917
TTrain::CheckOneSessionTrain
static bool CheckOneSessionTrain(std::ifstream &InFile)
Carries out an integrity check for the train section of a session file, if fails a message is given a...
Definition: TrainUnit.cpp:8552
TAllRoutes::GetRouteTypeAndGraphics
TRouteType GetRouteTypeAndGraphics(int Caller, int TrackVectorPosition, int LinkPos, Graphics::TBitmap *&EXGraphicPtr, Graphics::TBitmap *&EntryDirectionGraphicPtr)
Examines Route2MultiMap for the element at TrackVectorPosition with LinkPos (can be entry or exit).
Definition: TrackUnit.cpp:19558
TRailGraphics::CodeY
Graphics::TBitmap * CodeY
Definition: GraphicUnit.h:1024
TTrain::SetOneGraphicCode
Graphics::TBitmap * SetOneGraphicCode(char CodeChar)
Return a pointer to the graphic corresponding to the character 'CodeVhar'.
Definition: TrainUnit.cpp:2423
TTrain::DerailPending
bool DerailPending
Definition: TrainUnit.h:493
TTrainMode
TTrainMode
indicates train operating mode, 'None' for not in use
Definition: TrainUnit.h:59
TAllRoutes::TCallonEntry
Used to store relevant values when a call-on found, ready for plotting an unrestricted route.
Definition: TrackUnit.h:1692
TRailGraphics::Code_a
Graphics::TBitmap * Code_a
Definition: GraphicUnit.h:964
TTrain::StoppedAtLocation
bool StoppedAtLocation
Definition: TrainUnit.h:493
TTrainController::TContinuationTrainExpectationMultiMapIterator
TContinuationTrainExpectationMultiMap::iterator TContinuationTrainExpectationMultiMapIterator
iterator for the multimap
Definition: TrainUnit.h:766
TTrain::MaximumMassLimit
static const int MaximumMassLimit
kg (i.e. 10,000 tonnes)
Definition: TrainUnit.h:315
TTrainController::StopTTClockFlag
bool StopTTClockFlag
when true the timetable clock is stopped, used for messages display and train popup menu display etc
Definition: TrainUnit.h:810
FrontSplit
@ FrontSplit
Definition: TrainUnit.h:51
TTrain::MaxExitSpeed
double MaxExitSpeed
the maximum speed that the train can exit the next element
Definition: TrainUnit.h:437
TTrainController::BFHigh
bool BFHigh
Definition: TrainUnit.h:822
TRailGraphics::Code4
Graphics::TBitmap * Code4
Definition: GraphicUnit.h:994
TRailGraphics::Code_g
Graphics::TBitmap * Code_g
Definition: GraphicUnit.h:970
TTrain::LagEntryPos
int LagEntryPos
Definition: TrainUnit.h:373
SNTShuttle
@ SNTShuttle
Definition: TrainUnit.h:66
TTrain::NewDelay
double NewDelay
an additional random delay at a location (added at v2.13.0)
Definition: TrainUnit.h:449
TRailGraphics::CodeG
Graphics::TBitmap * CodeG
Definition: GraphicUnit.h:1006
Leave
@ Leave
Definition: TrainUnit.h:51
TTrainController::UnexpectedExits
int UnexpectedExits
Definition: TrainUnit.h:863
TTrainController::TLocServiceTimes::AtLocTime
AnsiString AtLocTime
Definition: TrainUnit.h:778
TTrainController::ServiceReference
AnsiString ServiceReference
String used to display the offending service in timetable error messages.
Definition: TrainUnit.h:806
TTrain::FirstLaterStopRecoverableTime
float FirstLaterStopRecoverableTime
this used to deduct from RecoverableTime when arrive at a location for OperatorActionpanel (OperatorA...
Definition: TrainUnit.h:463
TTrack::FailedSignalsVector
TFailedElementVector FailedSignalsVector
Definition: TrackUnit.h:794
TTrain::LeavingUnderSigControlAtContinuation
bool LeavingUnderSigControlAtContinuation
set when the train has reached an exit continuation when under signaller control, used to prevent the...
Definition: TrainUnit.h:403
TTrain::PlotTrainGraphic
void PlotTrainGraphic(int Caller, int ArrayNumber, TDisplay *Disp)
Plot the train's headcode character corresponding to ArrayNumber.
Definition: TrainUnit.cpp:3189
TRailGraphics::Code_j
Graphics::TBitmap * Code_j
Definition: GraphicUnit.h:973
FNil
@ FNil
Definition: Utilities.h:43
NoFormat
@ NoFormat
Definition: TrainUnit.h:66
TTrainController::TwoOrMoreLocationsWarningGiven
bool TwoOrMoreLocationsWarningGiven
new at v2.6.0 to allow loops
Definition: TrainUnit.h:818
FailBufferCrash
@ FailBufferCrash
Definition: TrainUnit.h:42
WaitingForFJO
@ WaitingForFJO
Definition: TrainUnit.h:42
FailMissedExitRailway
@ FailMissedExitRailway
Definition: TrainUnit.h:41
TTrainController::ControllerGetNewServiceDepartureInfo
AnsiString ControllerGetNewServiceDepartureInfo(int Caller, TActionVectorIterator Ptr, int RptNum, TTrainDataEntry *TDEPtr, TTrainDataEntry *LinkedTrainDataPtr, int IncrementalMinutes, int IncrementalDigits, AnsiString RetStr)
Similar to TTrain::GetNewServiceDepartureInfo for use in ContinuationEntryFloatingTTString.
Definition: TrainUnit.cpp:10672
TimeTimeLoc
@ TimeTimeLoc
Definition: TrainUnit.h:66
TTrain::GetOffsetValues
void GetOffsetValues(int Caller, int &HOffset, int &VOffset, int Link) const
Sets HOffset & VOffset (see above) for a single headcode character depending on the Link value.
Definition: TrainUnit.cpp:2734
TTrain::TimeToExit
float TimeToExit
in minutes: new for multiplayer, -1 = > 60 mins
Definition: TrainUnit.h:461
TTrain::Derailed
bool Derailed
Definition: TrainUnit.h:493
TRailGraphics::ChangeSpecificColour
void ChangeSpecificColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor ColourToBeChanged, TColor NewColour)
Definition: GraphicUnit.cpp:3572
Enter
@ Enter
Definition: TrainUnit.h:51
TRailGraphics::bm93set
Graphics::TBitmap * bm93set
Definition: GraphicUnit.h:518
TTrack::GetTrackVectorPositionFromString
int GetTrackVectorPositionFromString(int Caller, AnsiString String, bool GiveMessages)
Takes the ElementID value (an AnsiString) (e.g. "8-13", "N43-N127", etc) and returns the correspondin...
Definition: TrackUnit.cpp:8067
TRailGraphics::Code_q
Graphics::TBitmap * Code_q
Definition: GraphicUnit.h:980
TTrainController::SaveSessionLockedRoutes
void SaveSessionLockedRoutes(int Caller, std::ofstream &SessionFile)
save locked routes to a session file
Definition: TrainUnit.cpp:16966
TTrain::OriginalPowerAtRail
double OriginalPowerAtRail
new at v2.4.0 to store value before a failure so it can be restored from here when repaired
Definition: TrainUnit.h:455
TTrainFormattedInformation
Contains all information for a single timetable entry for use in the formatted timetable.
Definition: TrainUnit.h:278
TTrainController::IsSNTEntryLocated
bool IsSNTEntryLocated(int Caller, const TTrainDataEntry &TDEntry, AnsiString &LocationName)
New trains introduced with 'Snt' may be at a timetabled location or elsewhere. This function checks a...
Definition: TrainUnit.cpp:15604
TActionVectorEntry::NewDescription
AnsiString NewDescription
Definition: TrainUnit.h:121
TTrainController::TContinuationTrainExpectationEntry::VectorPosition
int VectorPosition
TrackVectorPosition for the continuation element.
Definition: TrainUnit.h:758
TTrain::NewTrainService
void NewTrainService(int Caller, bool NoLogFlag)
Carry out the actions needed when a train forms a new service (code Fns)
Definition: TrainUnit.cpp:6336
TTrainController::TTrainController
TTrainController()
Constructor.
Definition: TrainUnit.cpp:9676
Timetable
@ Timetable
Definition: TrainUnit.h:60
TUtilities::SaveFileDouble
void SaveFileDouble(std::ofstream &OutFile, double SaveDouble)
converts the double value to a string (if double stored directly it is truncated to 6 digits) then st...
Definition: Utilities.cpp:127
TTrainController::GetRepeatHeadCode
AnsiString GetRepeatHeadCode(int Caller, AnsiString BaseHeadCode, int RepeatNumber, int IncDigits)
Return the service headcode for the repeat service.
Definition: TrainUnit.cpp:15989
StartSequence
@ StartSequence
Definition: TrainUnit.h:77
TTrain::AbleToMoveButForSignal
bool AbleToMoveButForSignal(int Caller)
Indicates that a train is only prevented from moving by a signal - used to allow appropriate popup me...
Definition: TrainUnit.cpp:6980
TTrainController::OpActionPanelVisible
bool OpActionPanelVisible
new v2.2.0 flag to prevent time to act functions when not visible
Definition: TrainUnit.h:816
TTrack::GetAnyElementOppositeLinkPos
int GetAnyElementOppositeLinkPos(int Caller, int TrackVectorPosition, int LinkPos, bool &Derail)
Return the opposite link position for the element at TrackVectorPosition with link position LinkPos,...
Definition: TrackUnit.cpp:11812
TTrack::GapFlashRedPosition
int GapFlashRedPosition
TrackVectorPosition of the gap element that is flashing green or red.
Definition: TrackUnit.h:786
TActionVectorEntry::NumberOfRepeats
int NumberOfRepeats
the number of repeating services
Definition: TrainUnit.h:129
TAllRoutes::GetFixedRouteAt
const TOneRoute & GetFixedRouteAt(int Caller, int At) const
Returns a constant reference to the route at AllRoutesVector position 'At', after performing range ch...
Definition: TrackUnit.cpp:19404
TUtilities::clTransparent
TColor clTransparent
the display background colour, can be white, black or dark blue
Definition: Utilities.h:115
TTrainController::SignalStopWarning
bool SignalStopWarning
Definition: TrainUnit.h:808
TPrefDirElement::GetELinkPos
int GetELinkPos() const
Returns the ELink array position.
Definition: TrackUnit.h:275
TTrainController::CalcOperatingAndNotStartedTrainLateness
void CalcOperatingAndNotStartedTrainLateness(int Caller)
calculates additional lateness values for trains that haven't reached their destinations yet
Definition: TrainUnit.cpp:21561
ShuttleLinkTypeForRepeatEntry
@ ShuttleLinkTypeForRepeatEntry
Definition: TrainUnit.h:82
TTrain::SignallerStopBrakeRate
double SignallerStopBrakeRate
the train brake rate when stopping under signaller control
Definition: TrainUnit.h:445
TTrainController::TLocServiceTimes::Location
AnsiString Location
Definition: TrainUnit.h:776
TOneCompleteFormattedTrain
A single train with its headcode + list of actions for use in the formatted timetable.
Definition: TrainUnit.h:265
TTrainController::SignallerTrainRemovedOnAutoSigsRoute
bool SignallerTrainRemovedOnAutoSigsRoute
true if train was on an AutoSigsRoute when removed by the signaller
Definition: TrainUnit.h:814
TTrack::InactiveTrackVector
TTrackVector InactiveTrackVector
Definition: TrackUnit.h:828
TTrain::LogAction
void LogAction(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionType ActionType, AnsiString LocationName, AnsiString SplitDistribution, TDateTime TimetableNonRepeatTime, bool Warning)
Send a message to the performance log and performance file, and if the message is flagged as a warnin...
Definition: TrainUnit.cpp:5407
Terminate
@ Terminate
Definition: TrainUnit.h:51
TTrainController::MRSLow
bool MRSLow
Definition: TrainUnit.h:822
TTrack::ActiveTrackElementNameMap
TActiveTrackElementNameMap ActiveTrackElementNameMap
< map of coupled continuations
Definition: TrackUnit.h:800
TTrain::Crashed
bool Crashed
Definition: TrainUnit.h:493
TTrackElement::HLoc
int HLoc
Definition: TrackUnit.h:149
TTrain::HeadCodePosition
Graphics::TBitmap * HeadCodePosition[4]
Set from the HeadCodeGrPtr[4] pointer values, HeadCodePosition[0] is always the front,...
Definition: TrainUnit.h:509
TTrain::TrainID
int TrainID
the train's identification number
Definition: TrainUnit.h:375
Running
@ Running
Definition: TrainUnit.h:88
TAllFormattedTrains
std::vector< TTrainFormattedInformation > TAllFormattedTrains
vector of all timetabled trains for use in the formatted timetable
Definition: TrainUnit.h:287
TRailGraphics::ChangeBackgroundColour3
void ChangeBackgroundColour3(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewBackgroundColour, TColor OldBackgroundColour)
as above but uses Scanline
Definition: GraphicUnit.cpp:3669
TRailGraphics::smCyan
Graphics::TBitmap * smCyan
Definition: GraphicUnit.h:899
TTrainController::MovingSuccessor
bool MovingSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:14901
TTrainController::LogActionError
void LogActionError(int Caller, AnsiString HeadCode, AnsiString OtherHeadCode, TActionEventType ActionEventType, AnsiString LocationID)
Send an error message to the performance log and file, and as a warning if appropriate.
Definition: TrainUnit.cpp:16462
TTrainController::TrainVectorAt
TTrain & TrainVectorAt(int Caller, int VecPos)
Return a reference to the train at position VecPos in the TrainVector, carries out range checking on ...
Definition: TrainUnit.cpp:17242
TTrain::FrontElementSpeedLimit
int FrontElementSpeedLimit
Definition: TrainUnit.h:465
TTrack::NumberOfPlatforms
int NumberOfPlatforms(int Caller, AnsiString LocationName)
Returns the number of separate platforms (not platform elements) at a given location,...
Definition: TrackUnit.cpp:12032
TTrack::TSigElement::SpeedTag
int SpeedTag
the TrackElement SpeedTag value - specifies the signal element
Definition: TrackUnit.h:728
TTrain::DeleteTrain
void DeleteTrain(int Caller)
This is a housekeeping function to delete train heap objects (bitmaps) explicitly rather than by usin...
Definition: TrainUnit.cpp:243
TTrainController::NumFailures
int NumFailures
Definition: TrainUnit.h:864
TTrain::ExitSpeedFull
double ExitSpeedFull
speed when leaving the next element
Definition: TrainUnit.h:431
TTrain::SetHeadCodeGraphics
void SetHeadCodeGraphics(int Caller, AnsiString Code)
Set the four HeadCodeGrPtr[4] pointers to the appropriate character graphics with the current backgro...
Definition: TrainUnit.cpp:2620
TAllRoutes::SetTrailingSignalsOnAutoSigsRoute
void SetTrailingSignalsOnAutoSigsRoute(int Caller, int TrackVectorPosition, int XLinkPos)
Enter with signal at TrackVectorElement already set to red by the passing train.
Definition: TrackUnit.cpp:20415
clB5G5R5
#define clB5G5R5
Definition: GraphicUnit.h:287
TUtilities::TimeStamp
AnsiString TimeStamp()
creates a string of the form 'hh:mm:ss' for use in call & event logging
Definition: Utilities.cpp:73
TTrainController::RandomFailureCounter
unsigned int RandomFailureCounter
new at v2.4.0 for train failures, resets after 53 seconds (53 prime so can trigger at any clock time)
Definition: TrainUnit.h:880
TAllRoutes::TLockedRouteClass::RouteNumber
int RouteNumber
the vector position number of the relevant route in AllRoutesVector
Definition: TrackUnit.h:1657
TTrainController::AddTrain
bool AddTrain(int Caller, int RearPosition, int FrontPosition, AnsiString HeadCode, int StartSpeed, int Mass, double MaxRunningSpeed, double MaxBrakeRate, double PowerAtRail, AnsiString ModeStr, TTrainDataEntry *TrainDataEntryPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits, int SignallerSpeed, bool SignallerControl, TActionEventType &EventType)
Introduce a new train to the railway, with the characteristics specified, returns true for success,...
Definition: TrainUnit.cpp:10159
TTrainController::~TTrainController
~TTrainController()
Destructor.
Definition: TrainUnit.cpp:9729
TTrain::RepeatShuttleOrNewNonRepeatService
void RepeatShuttleOrNewNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:6805
TTrainController::TwoLocationList
TServiceCallingLocsList TwoLocationList
Definition: TrainUnit.h:874
TTrain::GetNewServiceDepartureInfo
AnsiString GetNewServiceDepartureInfo(int Caller, TActionVectorEntry *Ptr, int RptNum, TTrainDataEntry *LinkedTrainDataPtr, AnsiString RetStr, bool TimetableTime)
called during FloatingLabelNextString to find the next service departure time & next location (last b...
Definition: TrainUnit.cpp:7604
TTrain::Description
AnsiString Description
needs own HeadCode & description because repeat entries will differ from TrainDataEntry values (Descr...
Definition: TrainUnit.h:329
TUtilities::CheckAndReadFileInt
bool CheckAndReadFileInt(std::ifstream &InFile, int Lowest, int Highest, int &OutInt)
checks that the value is an int lying between Lowest & Highest (inclusive), returns true for success ...
Definition: Utilities.cpp:280
TRailGraphics::Code7
Graphics::TBitmap * Code7
Definition: GraphicUnit.h:997
TTrackElement::ActiveTrackElementName
AnsiString ActiveTrackElementName
Location name used either in the timetable or for a continuation (continuation names not used in time...
Definition: TrackUnit.h:128
TRailGraphics::bm94set
Graphics::TBitmap * bm94set
Definition: GraphicUnit.h:520
TUtilities::DelayMode
TDelayMode DelayMode
specifies whether no delays or minor, moderate or major random delays are to be applied (added at v2....
Definition: Utilities.h:117
TTrainController::LogEvent
void LogEvent(AnsiString Str)
store Str to the event log - moved from TUtilities for v0.6 so can record the tt clock value
Definition: TrainUnit.cpp:9740
TRailGraphics::CodeO
Graphics::TBitmap * CodeO
Definition: GraphicUnit.h:1014
TTrain::ExitPair
THVShortPair ExitPair
H & V coordinates of the exit element related to TimeToExit, new for multiplayer.
Definition: TrainUnit.h:479
TAllRoutes::SignallerRemovedTrainAutoRoute
TOneRoute SignallerRemovedTrainAutoRoute
if train was on an AutoSigsRoute when removed then this stores the route so that signals can be reset
Definition: TrackUnit.h:1744
TTrainController::MassHigh
bool MassHigh
Definition: TrainUnit.h:822
TTrainController::CheckAndPopulateListOfIDs
bool CheckAndPopulateListOfIDs(int Caller, AnsiString IDSet, TNumList &ExitList, bool GiveMessages)
Used to compile ExitList from a string list of element IDs, returns true for success or gives a messa...
Definition: TrainUnit.cpp:12343
TTrain::PlotTrainWithNewBackgroundColour
void PlotTrainWithNewBackgroundColour(int Caller, TColor NewBackgroundColour, TDisplay *Disp)
Changes the train's background colour (e.g. to pale green if stopped at a station) Note that this use...
Definition: TrainUnit.cpp:3688
TUtilities::SaveFileString
void SaveFileString(std::ofstream &OutFile, AnsiString SaveString)
stores the string value to the file, then a '0' delimiter then a CR
Definition: Utilities.cpp:135
TTrain::PlotAlternativeTrackRouteGraphic
void PlotAlternativeTrackRouteGraphic(int Caller, unsigned int LagElement, int LagELinkPos, int HOffset, int VOffset, TStraddle StraddleValue)
When a train moves off a bridge the other track may contain a route or have a train on it that has be...
Definition: TrainUnit.cpp:3388
TTrain::ExitTimeFull
TDateTime ExitTimeFull
times used in SetTrainMovementValues corresponding to the next element the train runs on
Definition: TrainUnit.h:473
TTrainController::CalcDistanceToRedSignalandStopTime
int CalcDistanceToRedSignalandStopTime(int Caller, int TrackVectorPosition, int TrackVectorPositionEntryPos, bool SigControlAndCanPassRedSignal, TActionVectorEntry *AVPtr, AnsiString HeadCode, int TrainID, float &CurrentStopTime, float &LaterStopTime, float &RecoverableTime, int &AvTrackSpeed, int &DistanceToExit, THVShortPair &ExitPair)
new v2.2.0 (DistanceToExit added for multiplayer), calcs distances to red signal & exit,...
Definition: TrainUnit.cpp:21874
TTrack::GapFlashRed
TGraphicElement * GapFlashRed
the red & green circle graphics used to show where the gaps are
Definition: TrackUnit.h:808
LocTypeForRepeatEntry
@ LocTypeForRepeatEntry
Definition: TrainUnit.h:72
SignallerChangeDirection
@ SignallerChangeDirection
Definition: TrainUnit.h:53
TAllRoutes::TLockedRouteClass
Handles routes that are locked because of approaching trains.
Definition: TrackUnit.h:1655
TTrain::TrainAtLocation
bool TrainAtLocation(int Caller, AnsiString &LocationName)
True when the train is stopped at a timetabled location.
Definition: TrainUnit.cpp:9133
TTrain::IsTrainIDOnBridgeTrackPos01
bool IsTrainIDOnBridgeTrackPos01(int Caller, unsigned int TrackVectorPosition)
True if train is on a bridge on trackpos 0 & 1.
Definition: TrainUnit.cpp:3252
TActionVectorEntry
Contains a single train action in a timetable - repeat entry is also of this class though no train ac...
Definition: TrainUnit.h:119
TTrainController::TotLateExitMins
float TotLateExitMins
Definition: TrainUnit.h:840
TTrainController::CheckSessionContinuationAutoSigEntries
bool CheckSessionContinuationAutoSigEntries(int Caller, std::ifstream &SessionFile)
Part of the session file integrity check for ContinuationAutoSigEntries, true for success.
Definition: TrainUnit.cpp:17088
TOneCompleteFormattedTrain::OneFormattedTrainVector
TOneFormattedTrainVector OneFormattedTrainVector
Definition: TrainUnit.h:268
TRailGraphics::Code1
Graphics::TBitmap * Code1
Definition: GraphicUnit.h:991
TTrainController::TimetableMessage
void TimetableMessage(bool GiveMessages, AnsiString Message)
Sends a message to the user if GiveMessages is true, including ServiceReference (see above) if not nu...
Definition: TrainUnit.cpp:16403
TDisplay
Definition: DisplayUnit.h:49
TTrackElement::ConnLinkPos
int ConnLinkPos[4]
Connecting element link position (i.e. array positions of the connecting element links,...
Definition: TrackUnit.h:147
TTrainController::ContinuationAutoSigVector
TContinuationAutoSigVector ContinuationAutoSigVector
vector for TContinuationAutoSigEntry objects
Definition: TrainUnit.h:882
TTrain::StoppedWithoutPower
bool StoppedWithoutPower
Definition: TrainUnit.h:494
TTrain::NameInTimetableBeforeCDT
int NameInTimetableBeforeCDT(int Caller, AnsiString Name, bool &Stop)
Returns the number by which the train ActionVectorEntryPtr needs to be incremented to point to the lo...
Definition: TrainUnit.cpp:4837
TimeCmdDescription
@ TimeCmdDescription
Definition: TrainUnit.h:67
TTrainFormattedInformation::Header
AnsiString Header
description, mass, power, brake rate etc
Definition: TrainUnit.h:280
TActionEventType
TActionEventType
Used for reporting error conditions & warnings.
Definition: TrainUnit.h:38
TTrainController::TrainExistsAtIdent
bool TrainExistsAtIdent(int Caller, int TrainID)
new at v2.4.0 return true if find the train (added at v2.4.0 as can select a removed train in Actions...
Definition: TrainUnit.cpp:10493
SignallerJoin
@ SignallerJoin
Definition: TrainUnit.h:52
TTrain::RearStartElement
int RearStartElement
start TrackVectorPosition element for rear of train
Definition: TrainUnit.h:363
TTrain::StepForwardFlag
bool StepForwardFlag
set when the signaller command to step forward one element has been given
Definition: TrainUnit.h:413
TTrainController::SplitTrainInfo
bool SplitTrainInfo(int Caller, AnsiString TrainInfoStr, AnsiString &HeadCode, AnsiString &Description, int &StartSpeed, int &MaxRunningSpeed, int &Mass, double &MaxBrakeRate, double &PowerAtRail, int &SignallerSpeed, bool GiveMessages)
Parse a train information entry, return true for success; PowerAtRail changed to double& from int& at...
Definition: TrainUnit.cpp:12443
TTrainController::TotEarlyExitMins
float TotEarlyExitMins
Definition: TrainUnit.h:836
TFixedTrackPiece::Config
TConfiguration Config[4]
the type of link - see TConfiguration above
Definition: TrackUnit.h:97
TTrainController::Operate
void Operate(int Caller)
called every clock tick to introduce new trains and update existing trains
Definition: TrainUnit.cpp:9754
TTrackElement::Failed
bool Failed
New parameter added at v2.13.0 for failed points, signals & TSRs.
Definition: TrackUnit.h:141
TTrackElement::TrainIDOnElement
int TrainIDOnElement
Definition: TrackUnit.h:155
TTrainController::SkippedTTEvents
int SkippedTTEvents
Definition: TrainUnit.h:859
TRailGraphics::smRed
Graphics::TBitmap * smRed
Definition: GraphicUnit.h:906
TTrain::PowerAtRail
double PowerAtRail
in Watts (taken as 80% of the train's Gross Power, i.e. that entered by the user)
Definition: TrainUnit.h:453
TRailGraphics::LCPlainMan
Graphics::TBitmap * LCPlainMan
Definition: GraphicUnit.h:750
TDisplay::GetOutputLog4
TLabel * GetOutputLog4()
Definition: DisplayUnit.h:160
TAllRoutes::RebuildRailwayFlag
bool RebuildRailwayFlag
this is set whenever a route has to be cancelled forcibly in order to force a ClearandRebuildRailway ...
Definition: TrackUnit.h:1725
TTrainDataEntry::MaxRunningSpeed
double MaxRunningSpeed
in km/h
Definition: TrainUnit.h:215
TUtilities::LoadFileInt
int LoadFileInt(std::ifstream &InFile)
loads an int value from the file
Definition: Utilities.cpp:162
TTrackElement::ElementID
AnsiString ElementID
the element identifier based on position in the railway
Definition: TrackUnit.h:130
TUtilities::MinorDelayCutoff
float MinorDelayCutoff
Definition: Utilities.h:50
TTimeToExitMultiMapEntry
std::pair< THVShortPair, TExitInfo > TTimeToExitMultiMapEntry
Definition: TrainUnit.h:113
TRailGraphics::Code6
Graphics::TBitmap * Code6
Definition: GraphicUnit.h:996
TTrainDataEntry::TrainOperatingDataVector
TTrainOperatingDataVector TrainOperatingDataVector
operating information for the train including all its repeats
Definition: TrainUnit.h:229
TTrain::DelayedRandMins
double DelayedRandMins
the remaining random delay at any point in time for the train (added at v2.13.0)
Definition: TrainUnit.h:447
TTrackElement::VLoc
int VLoc
The h & v locations in the railway (top lh corner of the first build screen = 0,0)
Definition: TrackUnit.h:149
TTrainController::OperatingTrainLateMins
float OperatingTrainLateMins
total late minutes of operating trains on exit operation for locations not reached yet
Definition: TrainUnit.h:829
TTrack::GetNonPointsOppositeLinkPos
int GetNonPointsOppositeLinkPos(int LinkPosIn)
Return the corresponding link position (track always occupies either links 0 & 1 or 2 & 3)
Definition: TrackUnit.h:910
TTrain::UnplotTrain
void UnplotTrain(int Caller)
Unplot train from screen in zoomed-in mode.
Definition: TrainUnit.cpp:562
TTrain::IsThereAnAdjacentTrain
bool IsThereAnAdjacentTrain(int Caller, TTrain *&TrainToBeJoinedBy)
Definition: TrainUnit.cpp:5323
TTrain::TRSTime
TDateTime TRSTime
Definition: TrainUnit.h:475
TAllRoutes::TLockedRouteClass::LastTrackVectorPosition
unsigned int LastTrackVectorPosition
the TrackVector position of the last (i.e. most forward) element in the route (this will be truncated...
Definition: TrackUnit.h:1661
FailMissedJBO
@ FailMissedJBO
Definition: TrainUnit.h:40
TUtilities::EventLog
std::deque< AnsiString > EventLog
event store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:107
TAllRoutes::NoRoute
@ NoRoute
Definition: TrackUnit.h:1671
TActionVectorEntry::FormatType
TTimetableFormatType FormatType
defines the timetable action type
Definition: TrainUnit.h:137
TTrain::RemainHereLogNotSent
bool RemainHereLogNotSent
flag to prevent repeated logs, new at v1.2.0
Definition: TrainUnit.h:341
TRailGraphics::CodeU
Graphics::TBitmap * CodeU
Definition: GraphicUnit.h:1020
TTrainController::RestartTime
TDateTime RestartTime
TTClockTime when operation pauses ( = timetable start time prior to operation) TTClockTime is calcula...
Definition: TrainUnit.h:721
Moderate
@ Moderate
Definition: Utilities.h:38
TTrainController::TimetableStartTime
TDateTime TimetableStartTime
the start time of the current timetable
Definition: TrainUnit.h:719
clSPADBackground
#define clSPADBackground
Definition: GraphicUnit.h:301
TTrainController::UnplotTrains
void UnplotTrains(int Caller)
unplot all trains from screen
Definition: TrainUnit.cpp:10143
TRailGraphics::smBrightGreen
Graphics::TBitmap * smBrightGreen
Definition: GraphicUnit.h:897
TTrainController::EarlyArrivals
int EarlyArrivals
Definition: TrainUnit.h:845
FailCreateTrain
@ FailCreateTrain
Definition: TrainUnit.h:39
TTrain::IncrementalDigits
int IncrementalDigits
the number of digits to increment by in repeat entries
Definition: TrainUnit.h:359
TTrainController::CreateFormattedTimetable
void CreateFormattedTimetable(int Caller, AnsiString RailwayTitle, AnsiString TimetableTitle, AnsiString CurDir)
Examines the internal timetable (TrainDataVector) and creates from it a chronological (....
Definition: TrainUnit.cpp:17255
TTrack::RouteFlashFlag
bool RouteFlashFlag
true while a route is flashing prior to being set
Definition: TrackUnit.h:770
TRailGraphics::Code_v
Graphics::TBitmap * Code_v
Definition: GraphicUnit.h:985
TTrackElement::StationEntryStopLinkPos1
int StationEntryStopLinkPos1
Definition: TrackUnit.h:153
Major
@ Major
Definition: Utilities.h:38
Points
@ Points
Definition: TrackUnit.h:66
TTrainController::SplitEntry
bool SplitEntry(int Caller, AnsiString OneEntry, bool GiveMessages, bool CheckLocationsExistInRailway, AnsiString &First, AnsiString &Second, AnsiString &Third, AnsiString &Fourth, int &RearStartOrRepeatMins, int &FrontStartPosition, TTimetableFormatType &TimetableFormatType, TTimetableLocationType &LocationType, TTimetableSequenceType &SequenceType, TTimetableShuttleLinkType &ShuttleLinkType, TNumList &ExitList, bool &Warning)
Parse a single timetable service action, return true for success.
Definition: TrainUnit.cpp:11848
TRailGraphics::LCPlain
Graphics::TBitmap * LCPlain
Definition: GraphicUnit.h:743
TTrain::LastActionDelayFlag
bool LastActionDelayFlag
used when trains join to ensure that there is a 30 second delay before the actual join takes place af...
Definition: TrainUnit.h:399
TTrainController::TotLatePassMins
float TotLatePassMins
Definition: TrainUnit.h:839
TTrainController::StripSpaces
void StripSpaces(int Caller, AnsiString &Input)
Strip both leading and trailing spaces at ends of Input and spaces before and after all commas and se...
Definition: TrainUnit.cpp:15503
FailMissedJoinOther
@ FailMissedJoinOther
Definition: TrainUnit.h:40
TTrain::JoinedOtherTrainFlag
bool JoinedOtherTrainFlag
true when the train has joined another train following an 'Fjo' timetable command or a signaller join...
Definition: TrainUnit.h:397
TTrainController::BFLow
bool BFLow
Definition: TrainUnit.h:822
TTrainController::TotArrDepPass
int TotArrDepPass
Definition: TrainUnit.h:862
SignallerPassRedSignal
@ SignallerPassRedSignal
Definition: TrainUnit.h:53
TRailGraphics::ChangeForegroundColour
void ChangeForegroundColour(int Caller, Graphics::TBitmap *BitmapIn, Graphics::TBitmap *BitmapOut, TColor NewForegroundColour, TColor BackgroundColour)
Definition: GraphicUnit.cpp:3517
TRailGraphics::CodeE
Graphics::TBitmap * CodeE
Definition: GraphicUnit.h:1004
FailLockedRoute
@ FailLockedRoute
Definition: TrainUnit.h:39
TTrain::SignallerRemoved
bool SignallerRemoved
set when removed under signaller control to force a removal from the display at the next clock tick
Definition: TrainUnit.h:407
clB5G3R0
#define clB5G3R0
Definition: GraphicUnit.h:268
TTrain::FrontCodePtr
Graphics::TBitmap * FrontCodePtr
points to the front headcode segment, this is set to red or blue depending on TrainMode
Definition: TrainUnit.h:513
TOneRoute::SetRouteSignals
void SetRouteSignals(int Caller) const
Called when setting a route to set all signals appropriately. Also called when a new train is added a...
Definition: TrackUnit.cpp:18036
Continuation
@ Continuation
Definition: TrackUnit.h:66
TTrainController::CallOnWarning
bool CallOnWarning
Definition: TrainUnit.h:808
TTrain::ActualArrivalTime
TDateTime ActualArrivalTime
location departure time and 'train ready to start' time (TRSTime is 10 seconds before the ReleaseTime...
Definition: TrainUnit.h:475
TTrack::GetFilletGraphic
Graphics::TBitmap * GetFilletGraphic(int Caller, TTrackElement TrackElement)
Return a pointer to the point fillet (the bit that appears to move when points are changed) for the p...
Definition: TrackUnit.cpp:7825
GraphicUnit.h
PerfLogUnit.h
TUtilities::MajorDelayFactor
float MajorDelayFactor
Definition: Utilities.h:55
TTrack::PointFlashFlag
bool PointFlashFlag
< true if a route set through an LC that is closed to trains (& therefore needs to be opened)
Definition: TrackUnit.h:768
TTrack::ThisLocationLongEnoughForSplit
bool ThisLocationLongEnoughForSplit(int Caller, AnsiString HeadCode, int TrainID, AnsiString LocationName, int LeadElement, int LeadExitPos, int MidElement, int MidEntryPos, int &FrontTrainFrontPos, int &FrontTrainRearPos, int &RearTrainFrontPos, int &RearTrainRearPos, bool &TemporaryDelay)
checks if the track that the train is on is long enough for a split, returns false if not,...
Definition: TrackUnit.cpp:11258
TTrainController::OnTimeDeps
int OnTimeDeps
Definition: TrainUnit.h:855
TTrain::RestoreTimetableLocation
AnsiString RestoreTimetableLocation
stores the location name at which signaller control is taken, to ensure that it is back at that locat...
Definition: TrainUnit.h:485
TTrainController::TimetableIntegrityCheck
bool TimetableIntegrityCheck(int Caller, char *FileName, bool GiveMessages, bool CheckLocationsExistInRailway)
Checks overall timetable integrity, calls many other specific checking functions, returns true for su...
Definition: TrainUnit.cpp:10911
AllRoutes
TAllRoutes * AllRoutes
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:55
TTrain::SignallerMaxSpeed
int SignallerMaxSpeed
maximum train speed under signaller control (in km/h)
Definition: TrainUnit.h:369
NoEvent
@ NoEvent
Definition: TrainUnit.h:39
FailSplitDueToOtherTrain
@ FailSplitDueToOtherTrain
Definition: TrainUnit.h:39
TTrainController::SendPerformanceSummary
void SendPerformanceSummary(int Caller, std::ofstream &PerfFile)
At the end of operation a summary of overall performance is sent to the performance file by this func...
Definition: TrainUnit.cpp:21065
TTrack::TInfrastructureFailureEntry::FailureTime
TDateTime FailureTime
Definition: TrackUnit.h:716
TTrain::ResetTrainElementID
void ResetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
After a train has moved off an element that element has its TrainIDOnElement value set back to -1 to ...
Definition: TrainUnit.cpp:3346
TRailGraphics::smMagenta
Graphics::TBitmap * smMagenta
Definition: GraphicUnit.h:902
RepairFailedTrain
@ RepairFailedTrain
Definition: TrainUnit.h:53
TTrainController::AllServiceCallingLocsMap
TAllServiceCallingLocsMap AllServiceCallingLocsMap
Definition: TrainUnit.h:789
TRailGraphics::smPaleGreen
Graphics::TBitmap * smPaleGreen
Definition: GraphicUnit.h:905
TRailGraphics::CodeS
Graphics::TBitmap * CodeS
Definition: GraphicUnit.h:1018
TTrainController::ContinuationEntryFloatingTTString
AnsiString ContinuationEntryFloatingTTString(int Caller, TTrainDataEntry *TTDEPtr, int RepeatNumber, int IncrementalMinutes, int IncrementalDigits)
Build string for use in floating window for expected trains at continuations.
Definition: TrainUnit.cpp:10524
TFixedTrackPiece::SpeedTag
int SpeedTag
The element identification number - corresponds to the relevant SpeedButton->Tag.
Definition: TrackUnit.h:88
TTrain::FollowOnServiceRef
AnsiString FollowOnServiceRef
Definition: TrainUnit.h:331
TTrain::SPADFlag
bool SPADFlag
set when running past a red signal without permission flags to indicate relevant stop conditions or p...
Definition: TrainUnit.h:491
TTrain::Stopped
bool Stopped()
True if the train has stopped for any reason.
Definition: TrainUnit.h:688
Minor
@ Minor
Definition: Utilities.h:38
TTrack::SigTableGroundSignal
TSigElement SigTableGroundSignal[40]
new at version 0.6 for ground signals
Definition: TrackUnit.h:742
TTrain::TrainToJoinIsAdjacent
bool TrainToJoinIsAdjacent(int Caller, TTrain *&TrainToJoin)
True for a train waiting to join another when the other train is adjacent.
Definition: TrainUnit.cpp:6620
TActionVectorEntry::ExitList
TNumList ExitList
the list of valid train exit TrackVector positions for 'Fer' entries (empty to begin with)
Definition: TrainUnit.h:135
TTrainController::OnTimeExits
int OnTimeExits
Definition: TrainUnit.h:857
TTrain::OldZoomOutElement
int OldZoomOutElement[3]
stores the Lead, Mid & Lag TrackVectorPositions, used for unplotting trains from the old position in ...
Definition: TrainUnit.h:500
TPrefDirElement::GetELink
int GetELink() const
Returns ELink.
Definition: TrackUnit.h:269
TDisplay::GetOutputLog8
TLabel * GetOutputLog8()
Definition: DisplayUnit.h:180
WaitingForJBO
@ WaitingForJBO
Definition: TrainUnit.h:42
TUtilities::CheckFileDouble
bool CheckFileDouble(std::ifstream &InFile)
checks that the value is a double, returns true for success
Definition: Utilities.cpp:331
TTrainDataEntry::ExplicitDescription
bool ExplicitDescription
< headcode is the first train's headcode, rest are calculated from repeat information; ServiceReferen...
Definition: TrainUnit.h:211
TTrain::Straddle
TStraddle Straddle
the current Straddle value of the train (see TStraddle above)
Definition: TrainUnit.h:520
SignallerStop
@ SignallerStop
Definition: TrainUnit.h:53
TRailGraphics::CodeB
Graphics::TBitmap * CodeB
Definition: GraphicUnit.h:1001
TAllRoutes::DiagonalFouledByRouteOrTrain
bool DiagonalFouledByRouteOrTrain(int Caller, int HLoc, int VLoc, int DiagonalLinkNumber)
The track geometry allows diagonals to cross without occupying the same track element,...
Definition: TrackUnit.cpp:21093
TTrain::RevisedStoppedAtLoc
bool RevisedStoppedAtLoc() const
Definition: TrainUnit.h:528
Display
TDisplay * Display
The object pointer for the on-screen display, object created in InterfaceUnit.
Definition: DisplayUnit.cpp:54
TTrack::GetVLocMin
int GetVLocMin()
Definition: TrackUnit.h:904
TRailGraphics::Code3
Graphics::TBitmap * Code3
Definition: GraphicUnit.h:993
TUtilities::CheckFileBool
bool CheckFileBool(std::ifstream &InFile)
checks that the value is a bool returns true for success
Definition: Utilities.cpp:209
TTrain::MaximumPowerLimit
static const int MaximumPowerLimit
Watts (i.e. 100MW)
Definition: TrainUnit.h:317
TTrain::PlotStartPosition
void PlotStartPosition(int Caller)
Plots the train and sets up all relevant members for a new train when it is introduced into the railw...
Definition: TrainUnit.cpp:289
TTrack::TActiveTrackElementNameMapEntry
std::pair< AnsiString, int > TActiveTrackElementNameMapEntry
Definition: TrackUnit.h:710
TTrainController::SingleServiceOutput
void SingleServiceOutput(int Caller, int SSVectorNumber, TNumList MarkerList, TTrainDataVector &SingleServiceVector, std::ofstream &VecFile)
Outputs the single service vector for train direction analysis purposes in timetable conflict analysi...
Definition: TrainUnit.cpp:20206
TTrack::TInfrastructureFailureEntry::RepairTime
TDateTime RepairTime
Definition: TrackUnit.h:717
TTrainController::TContinuationAutoSigEntry::SecondDelay
double SecondDelay
Definition: TrainUnit.h:730
TTrain::UnplotTrainInZoomOutMode
void UnplotTrainInZoomOutMode(int Caller)
Unplot train from screen in zoomed-out mode.
Definition: TrainUnit.cpp:9096
TTimetableFormatType
TTimetableFormatType
Timetable entry types.
Definition: TrainUnit.h:65
TTrainController::TrainFailedWarning
bool TrainFailedWarning
Flags to enable the relevant warning graphics to flash at the left hand side of the screen.
Definition: TrainUnit.h:808
TTrain::NextTrainID
static int NextTrainID
< km/h
Definition: TrainUnit.h:326
TTrainController::TLocServiceTimes
Class used for timetable conflict file compilation.
Definition: TrainUnit.h:775
TTrainOperatingData::RunningEntry
TRunningEntry RunningEntry
Definition: TrainUnit.h:187
NotAShuttleLink
@ NotAShuttleLink
Definition: TrainUnit.h:82
TRailGraphics::gl91set
Graphics::TBitmap * gl91set
Definition: GraphicUnit.h:726
TRailGraphics::Code_y
Graphics::TBitmap * Code_y
Definition: GraphicUnit.h:988
TAllRoutes::CheckMapAndRoutes
void CheckMapAndRoutes(int Caller)
Diagnostic function - checks equivalence for each route between entries in PrefDirVector and those in...
Definition: TrackUnit.cpp:20189
TAllRoutes::GetRouteElementDataFromRoute2MultiMap
TRouteElementPair GetRouteElementDataFromRoute2MultiMap(int Caller, int HLoc, int VLoc, TRouteElementPair &SecondPair)
Retrieve up to two TRouteElementPair entries from Route2MultiMap at H & V, the first as a function re...
Definition: TrackUnit.cpp:20145
TTrain::CumulativeDelayedRandMinsOneTrain
double CumulativeDelayedRandMinsOneTrain
the running total of all random delays including knock-on delays for a single train,...
Definition: TrainUnit.h:451
TTrainController::TrainAdded
bool TrainAdded
true when a train has been added by a split (occurs outside the normal train introduction process)
Definition: TrainUnit.h:812
TActionVectorEntry::LocationName
AnsiString LocationName
Definition: TrainUnit.h:121
TTrainFormattedInformation::OneCompleteFormattedTrainVector
TOneCompleteFormattedTrainVector OneCompleteFormattedTrainVector
Definition: TrainUnit.h:284
ShuttleFinishedRemainingHere
@ ShuttleFinishedRemainingHere
Definition: TrainUnit.h:42
FailUnexpectedBuffers
@ FailUnexpectedBuffers
Definition: TrainUnit.h:40
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit01
Definition: TrackUnit.h:155
TRailGraphics::TempBackground
Graphics::TBitmap * TempBackground
Definition: GraphicUnit.h:909
TTrackElement::StationEntryStopLinkPos4
int StationEntryStopLinkPos4
Used for track at platforms ( 1 & 2) and non-station named locations (1 - 4) to mark the train front ...
Definition: TrackUnit.h:153
TTrain::TimeTimeLocArrived
bool TimeTimeLocArrived
indicates whether has arrived (true) or not when ActionVectorEntryPtr->FormatType == TimeTimeLoc
Definition: TrainUnit.h:339
TActionType
TActionType
Used in LogAction when reporting a train action to the performance log & file.
Definition: TrainUnit.h:50
TTrainController::TContinuationAutoSigEntry::ThirdDelay
double ThirdDelay
Delays in seconds before consecutive signal changes - these correspond to the times taken for trains ...
Definition: TrainUnit.h:730
TTrain::LeadEntryPos
int LeadEntryPos
Definition: TrainUnit.h:373
TTrainController::LateArrivals
int LateArrivals
Definition: TrainUnit.h:849
TRailGraphics::CodeZ
Graphics::TBitmap * CodeZ
Definition: GraphicUnit.h:1025
NoShuttleLink
@ NoShuttleLink
Definition: TrainUnit.h:82
TTrainController::ReplotTrains
void ReplotTrains(int Caller, TDisplay *Disp)
plot all trains on the display
Definition: TrainUnit.cpp:10110
TTrainController::CheckShuttleServiceIntegrity
bool CheckShuttleServiceIntegrity(int Caller, TTrainDataEntry *TDEntryPtr, bool GiveMessages)
Check that each shuttle service ends either in Fns or Fxx-sh (though a single service can't end in Fx...
Definition: TrainUnit.cpp:16350
TTrain::FloatingTimetableString
AnsiString FloatingTimetableString(int Caller, TActionVectorEntry *Ptr)
Used in the floating window to display the timetable.
Definition: TrainUnit.cpp:7820
TAllRoutes::GetModifiableRouteAt
TOneRoute & GetModifiableRouteAt(int Caller, int At)
Returns a modifiable reference to the route at AllRoutesVector position 'At', after performing range ...
Definition: TrackUnit.cpp:19417
TTrain::NewShuttleFromNonRepeatService
void NewShuttleFromNonRepeatService(int Caller, bool NoLogFlag)
Carry out the actions needed when a new shuttle service is created from a non-repeating (F-nshs) serv...
Definition: TrainUnit.cpp:6677
TTrain::AValue
double AValue
< only true when a train has become a follow-on service early and the follow-on service normally pass...
Definition: TrainUnit.h:423
FailUnexpectedExitRailway
@ FailUnexpectedExitRailway
Definition: TrainUnit.h:40
TTrain::FrontTrainSplit
void FrontTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the front.
Definition: TrainUnit.cpp:5805
TTrainController::WithinTimeRange
bool WithinTimeRange(int Caller, AnsiString Time1, AnsiString Time2, int MinuteRange)
check whether the two times are within the range in minutes specified and return true if so....
Definition: TrainUnit.cpp:20350
TTrainController::WriteTrainsToImage
void WriteTrainsToImage(int Caller, Graphics::TBitmap *Bitmap)
Called by TInterface::SaveOperatingImage1Click) to write all trains to the image file.
Definition: TrainUnit.cpp:10128
TActionVectorEntry::LinkedTrainEntryPtr
TTrainDataEntry * LinkedTrainEntryPtr
link pointer for use between fsp/rsp & Sfs; Fjo & jbo; Fns & Sns; & all shuttle to shuttle links
Definition: TrainUnit.h:145
TTrainController::AtLocSuccessor
bool AtLocSuccessor(const TActionVectorEntry &AVEntry)
A shorthand function that returns true if the successor to a given timetable action command should be...
Definition: TrainUnit.cpp:14908
TTrain::ZeroPowerNoJoinedByMessage
bool ZeroPowerNoJoinedByMessage
Definition: TrainUnit.h:348
TTrainController::SaveSessionContinuationAutoSigEntries
void SaveSessionContinuationAutoSigEntries(int Caller, std::ofstream &SessionFile)
save ContinuationAutoSigEntries to a session file
Definition: TrainUnit.cpp:17048
TTrainOperatingData::TrainID
int TrainID
Definition: TrainUnit.h:185
TUtilities::MaxRandomRepairTime
int MaxRandomRepairTime
Definition: Utilities.h:71
TDisplay::GetOutputLog3
TLabel * GetOutputLog3()
Definition: DisplayUnit.h:155
NoLocation
@ NoLocation
Definition: TrainUnit.h:72
TTrainController::CheckCrossReferencesAndSetData
bool CheckCrossReferencesAndSetData(int Caller, AnsiString SoughtHeadCode, AnsiString SeekingHeadCode, bool Shuttle, bool SetDataAndCheckLocations, bool GiveMessages)
A timetable validation function where all service cross references are checked for validity and set p...
Definition: TrainUnit.cpp:15029
TRailGraphics::bmTransparentBgnd
Graphics::TBitmap * bmTransparentBgnd
Definition: GraphicUnit.h:936
TTrainController::MinsToAnsiTime
AnsiString MinsToAnsiTime(int Input)
converts an integer minute value to string "HH:MM" added at v2.15.0
Definition: TrainUnit.cpp:16435
TTrain::FirstHalfMove
bool FirstHalfMove
true when the train is on the first half of an element when it displays as fully on two elements....
Definition: TrainUnit.h:395
FailLocTooShort
@ FailLocTooShort
Definition: TrainUnit.h:39
TTrainDataEntry::Mass
int Mass
in kg
Definition: TrainUnit.h:219
NoMode
@ NoMode
Definition: TrainUnit.h:60
TTrainController::LatePasses
int LatePasses
Definition: TrainUnit.h:851
TTrainController::SplitRepeat
bool SplitRepeat(int Caller, AnsiString OneEntry, int &RearStartOrRepeatMins, int &FrontStartOrRepeatDigits, int &RepeatNumber, bool GiveMessages)
Parse a timetable repeat entry, return true for success.
Definition: TrainUnit.cpp:12793
TFixedTrackPiece::TrackType
TTrackType TrackType
the type of track element
Definition: TrackUnit.h:100
TTrainController::TLocServiceTimes::ArrTime
AnsiString ArrTime
Definition: TrainUnit.h:779
TTrainController::SSHigh
bool SSHigh
Definition: TrainUnit.h:822
TimeLoc
@ TimeLoc
Definition: TrainUnit.h:66
TActionVectorIterator
TActionVector::iterator TActionVectorIterator
iterator
Definition: TrainUnit.h:176
TPrefDirElement
Basic preferred direction or route element - track element with additional members.
Definition: TrackUnit.h:201
TTrainController::AvHoursIntValue
int AvHoursIntValue
Input in MTBFEditBox in timetable hours, min value is 1 and max is 10,000. Here because performance f...
Definition: TrainUnit.h:872
TTrainController::CrashedTrains
int CrashedTrains
Definition: TrainUnit.h:843
TTrainController::TTEditPanelVisible
bool TTEditPanelVisible
new at v2.6.0 so potential error message only shows in TTEdit mode
Definition: TrainUnit.h:820
TDisplay::WarningLog
void WarningLog(int Caller, AnsiString Statement)
Display warning message Statement in the bottom left hand warning position and scroll other messages ...
Definition: DisplayUnit.cpp:523
TTrain::StoppedAtSignal
bool StoppedAtSignal
Definition: TrainUnit.h:493
TTrainController::GetControllerTrainTime
TDateTime GetControllerTrainTime(int Caller, TDateTime Time, int RepeatNumber, int IncrementalMinutes)
Get the interval between repeats.
Definition: TrainUnit.cpp:10512
TRailGraphics::Code_u
Graphics::TBitmap * Code_u
Definition: GraphicUnit.h:984
TTrackElement::Attribute
int Attribute
special variable used only for points, signals & level crossings, ignored otherwise; points 0=set to ...
Definition: TrackUnit.h:143
TStraddle
TStraddle
Defines the train position with respect to the track elements; three consecutive elements are Lead (f...
Definition: TrainUnit.h:299
FailEntryRouteSetAgainst
@ FailEntryRouteSetAgainst
Definition: TrainUnit.h:43
TTrain::TrainSkippedEvents
int TrainSkippedEvents
stores the pointer increment from the current action in ActionVector for skipped actions when a depar...
Definition: TrainUnit.h:383
TTrainController::THCandTrainPosParam
std::pair< AnsiString, int > THCandTrainPosParam
Definition: TrainUnit.h:791
TExitInfo::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:105
TTrainDataVector
std::vector< TTrainDataEntry > TTrainDataVector
vector class for containing the whole timetable - one entry per timetable service entry (the object i...
Definition: TrainUnit.h:242
TTrainController::GetRepeatTime
TDateTime GetRepeatTime(int Caller, TDateTime BasicTime, int RepeatNumber, int IncMinutes)
Return the repeating service time.
Definition: TrainUnit.cpp:16023
clCrashedBackground
#define clCrashedBackground
Definition: GraphicUnit.h:294
TTrack::IsLCAtHV
bool IsLCAtHV(int Caller, int HLoc, int VLoc)
True if a level crossing is found at H & V.
Definition: TrackUnit.cpp:7564
TTrain::OpTimeToAct
float OpTimeToAct
in minutes: new at v2.2.0 for operator time to act panel. Calculated in UpdateTrain,...
Definition: TrainUnit.h:459
TTrainController::TTClockTime
TDateTime TTClockTime
the time indicated by the timetable clock
Definition: TrainUnit.h:717
TAllRoutes::TLockedRouteClass::LockStartTime
TDateTime LockStartTime
the timetable time at which the route is locked, to start the 2 minute clock
Definition: TrackUnit.h:1665
TActionVectorEntry::SequenceType
TTimetableSequenceType SequenceType
indicates where in the sequence of codes the action lies
Definition: TrainUnit.h:141
TTrain::RearStartExitPos
int RearStartExitPos
the LinkPos value for the rear starting element (i.e. links to the front starting element)
Definition: TrainUnit.h:365
TTrainController::TLocServiceTimes::FrhMarker
AnsiString FrhMarker
Definition: TrainUnit.h:781
TRailGraphics::Code_o
Graphics::TBitmap * Code_o
Definition: GraphicUnit.h:978
TRailGraphics::CodeV
Graphics::TBitmap * CodeV
Definition: GraphicUnit.h:1021
TTrainController::TLocServiceTimes::ServiceAndRepeatNum
AnsiString ServiceAndRepeatNum
Definition: TrainUnit.h:777
TTrain::SetTrainElementID
void SetTrainElementID(int Caller, unsigned int TrackVectorPosition, int EntryPos)
When a train moves onto an element that element has its TrainIDOnElement value set to the TrainID val...
Definition: TrainUnit.cpp:3310
TActionVectorEntry::NonRepeatingShuttleLinkHeadCode
AnsiString NonRepeatingShuttleLinkHeadCode
Definition: TrainUnit.h:121
TActionVectorEntry::ShuttleLinkType
TTimetableShuttleLinkType ShuttleLinkType
indicates whether or not the action relates to a shuttle service link
Definition: TrainUnit.h:143
TTrain::TimetableFinished
bool TimetableFinished
set when there are no more timetable actions
Definition: TrainUnit.h:417
TUtilities::CallLog
std::deque< AnsiString > CallLog
call stack store, saved to the errorlog for diagnostic purposes
Definition: Utilities.h:105
TRailGraphics::CodeN
Graphics::TBitmap * CodeN
Definition: GraphicUnit.h:1013
TActionVectorEntry::Command
AnsiString Command
Definition: TrainUnit.h:121
TTrain::RepeatShuttleOrRemainHere
void RepeatShuttleOrRemainHere(int Caller, bool NoLogFlag)
Carry out the actions needed to create either a new shuttle service or (if all repeats have finished)...
Definition: TrainUnit.cpp:6731
TTrain::CallingOnAllowed
bool CallingOnAllowed(int Caller)
True if the train can be called on at its current position - see detail in .cpp file.
Definition: TrainUnit.cpp:5032
TTrainController::TContinuationTrainExpectationEntry::HeadCode
AnsiString HeadCode
< service description
Definition: TrainUnit.h:750
TTrackElement::Length23
int Length23
Definition: TrackUnit.h:151
TTrain::SignallerStoppingFlag
bool SignallerStoppingFlag
set when the signaller stop command has been given
Definition: TrainUnit.h:409
TTrainController::NotStartedTrainLateArr
int NotStartedTrainLateArr
total number of arrivals & departures for trains that haven't started yet for locations not reached y...
Definition: TrainUnit.h:868
TUtilities::LoadFileBool
bool LoadFileBool(std::ifstream &InFile)
loads a bool value from the file
Definition: Utilities.cpp:145
TTrainController::SigSLow
bool SigSLow
Message flags in TT checks to stop being given twice.
Definition: TrainUnit.h:822
TRailGraphics::Code_m
Graphics::TBitmap * Code_m
Definition: GraphicUnit.h:976
TTrack::AnyLinkedBarrierDownVectorManual
bool AnyLinkedBarrierDownVectorManual(int Caller, int HLoc, int VLoc, int &BDVectorPos)
Checks BarrierDownVector and returns true if there is one that is linked to the LC at H & V positions...
Definition: TrackUnit.cpp:6588
TRailGraphics::gl92set
Graphics::TBitmap * gl92set
Definition: GraphicUnit.h:728
SNSShuttle
@ SNSShuttle
Definition: TrainUnit.h:66
RemoveTrain
@ RemoveTrain
Definition: TrainUnit.h:52
TTrainController::TLocServiceTimes::DepTime
AnsiString DepTime
Definition: TrainUnit.h:780
TTrainController::SetWarningFlags
void SetWarningFlags(int Caller)
This sets all the warning flags (CrashWarning, DerailWarning etc) to their required states after a se...
Definition: TrainUnit.cpp:21515
TDisplay::PlotSmallOutput
void PlotSmallOutput(int Caller, int HPos, int VPos, Graphics::TBitmap *PlotItem)
Plot small (4x4) graphic PlotItem on the zoomed-out display at HPos & Vpos.
Definition: DisplayUnit.cpp:114
THVShortPair
std::pair< int, int > THVShortPair
Definition: InterfaceUnit.h:82
TTrainController::TContinuationTrainExpectationEntry
Class that stores data for trains expected at continuation entries (kept in a multimap - see below),...
Definition: TrainUnit.h:746
TTrainController::TContinuationTrainExpectationEntry::FixedDescription
AnsiString FixedDescription
Definition: TrainUnit.h:748
SNSNonRepeatFromShuttle
@ SNSNonRepeatFromShuttle
Definition: TrainUnit.h:66
TTrainController::TotEarlyPassMins
float TotEarlyPassMins
Definition: TrainUnit.h:835
TRailGraphics::CodeH
Graphics::TBitmap * CodeH
Definition: GraphicUnit.h:1007
TTrainController::OpTimeToActMultiMap
TOpTimeToActMultiMap OpTimeToActMultiMap
added v2.2.0 for Op time to act display
Definition: TrainUnit.h:886
TTrainController::TContinuationAutoSigEntry::AccessNumber
int AccessNumber
the number of times the signal changing function has been accessed - starts at 0 and increments after...
Definition: TrainUnit.h:732
TActionVectorEntry::Reminder
unsigned int Reminder
Definition: TrainUnit.h:149
TActionVectorEntry::SignallerControl
bool SignallerControl
< string values for timetabled event entries, null on creation
Definition: TrainUnit.h:125
TTrain::GetTrainTime
TDateTime GetTrainTime(int Caller, TDateTime Time)
Returns the timetable action time corresponding to 'Time' for this train, i.e. it adjusts the time va...
Definition: TrainUnit.cpp:5312
TTrain::BrakeRate
double BrakeRate
the current train brake rate
Definition: TrainUnit.h:441
TUtilities::LastDelayTTClockTime
double LastDelayTTClockTime
Clock time at which the latest delay for any train occurred. Used to prevent new delays within 5 minu...
Definition: Utilities.h:85
TTrackElement::SpeedLimit23
int SpeedLimit23
Element lengths and speed limits, ...01 is for the track with link positions [0] and [1],...
Definition: TrackUnit.h:151
TTrainController::TContinuationAutoSigEntry::PassoutTime
TDateTime PassoutTime
the timetable clock time at which the train exits from the continuation
Definition: TrainUnit.h:736
TTrain::FinishJoinLogSent
bool FinishJoinLogSent
Definition: TrainUnit.h:343
TTrack::GapFlashFlag
bool GapFlashFlag
true when a pair of connected gaps is flashing
Definition: TrackUnit.h:756
TRailGraphics::gl95set
Graphics::TBitmap * gl95set
Definition: GraphicUnit.h:732
TrainUnit.h
PassTime
@ PassTime
Definition: TrainUnit.h:67
TTrainController::IncorrectExits
int IncorrectExits
Definition: TrainUnit.h:848
TTrain::ContinuationExit
bool ContinuationExit(int Caller, int Element, int Exitpos) const
True if Element is a continuation and Exitpos is the continuation end.
Definition: TrainUnit.cpp:3234
TFixedTrackPiece::Link
int Link[4]
Track connection link values, max. of 4, unused = -1, top lh diag = 1, top = 2, top rh diag = 3,...
Definition: TrackUnit.h:90
TTrain::UpdateTrain
void UpdateTrain(int Caller)
Major function called at each clock tick for each train & handles all train movement & associated act...
Definition: TrainUnit.cpp:654
TRailGraphics::LockedRouteCancelPtr
Graphics::TBitmap * LockedRouteCancelPtr[10]
for locked route cancel graphic, 1 for each of 8 links, 0 & 5 included as for direction
Definition: GraphicUnit.h:1074
TRailGraphics::Code8
Graphics::TBitmap * Code8
Definition: GraphicUnit.h:998
TTrainController::SameDirection
bool SameDirection(int Caller, AnsiString Ref1, AnsiString Ref2, AnsiString Time1, AnsiString Time2, int RepeatNum1, int RepeatNum2, TServiceCallingLocsList List1, TServiceCallingLocsList List2, AnsiString Location, bool Arrival)
Determines whether two services are running in the same direction when they arrive or depart from Loc...
Definition: TrainUnit.cpp:20634
TTrain::TrainToBeJoinedByIsAdjacent
bool TrainToBeJoinedByIsAdjacent(int Caller, TTrain *&TrainToBeJoinedBy)
True for a train waiting to be joined when the joining train is adjacent.
Definition: TrainUnit.cpp:6648
TTrackElement::TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
int TrainIDOnBridgeOrFailedPointOrigSpeedLimit23
Definition: TrackUnit.h:155
TTrain::SetTrainMovementValues
void SetTrainMovementValues(int Caller, int TrackVectorPosition, int EntryPos)
Calculates train speeds and times for the element that the train is about to enter....
Definition: TrainUnit.cpp:3730
TActionVectorEntry::Warning
bool Warning
if set triggers an alert in the warning and perf log panels when the action is reached
Definition: TrainUnit.h:127
TTrain::PickUpBackgroundBitmap
void PickUpBackgroundBitmap(int Caller, int HOffset, int VOffset, int Element, int EntryPos, Graphics::TBitmap *GraphicPtr) const
Store the background bitmap pointer (BackgroundPtr - see above) prior to being overwritten by the tra...
Definition: TrainUnit.cpp:2822
clStoppedTrainInFront
#define clStoppedTrainInFront
Definition: GraphicUnit.h:303
TDisplay::GetOutputLog10
TLabel * GetOutputLog10()
Definition: DisplayUnit.h:190
TTrainDataEntry::ServiceReference
AnsiString ServiceReference
Definition: TrainUnit.h:209
TTrain::SignallerChangeTrainDirection
void SignallerChangeTrainDirection(int Caller)
Unplots & replots train, which checks for facing signal and sets StoppedAtSignal if req'd.
Definition: TrainUnit.cpp:7011
TTrain::ZeroPowerNoRearSplitMessage
bool ZeroPowerNoRearSplitMessage
Definition: TrainUnit.h:346
FailMissedChangeDirection
@ FailMissedChangeDirection
Definition: TrainUnit.h:41
NotSet
@ NotSet
Definition: TrackUnit.h:76
Repeat
@ Repeat
Definition: TrainUnit.h:67
TRailGraphics::Code_c
Graphics::TBitmap * Code_c
Definition: GraphicUnit.h:966
TTrainController::StripExcessFromHeadCode
void StripExcessFromHeadCode(int Caller, AnsiString &HeadCode)
change an extended headcode to an ordinary 4 character headcode
Definition: TrainUnit.cpp:14917
FinishSequence
@ FinishSequence
Definition: TrainUnit.h:77
TTrack::TIMPair
std::pair< unsigned int, unsigned int > TIMPair
TrackElement pair type used for inactive elements, values are vector positions.
Definition: TrackUnit.h:678
TUtilities::Clock2Stopped
bool Clock2Stopped
when true the main loop - Interface->ClockTimer2 - is stopped
Definition: Utilities.h:75
TTrainController::MRSHigh
bool MRSHigh
Definition: TrainUnit.h:822
TTrack::OtherTrainOnTrack
bool OtherTrainOnTrack(int Caller, int TrackPos, int LinkPos, int OwnTrainID)
True if another train on LinkPos track of element at TrackPos, whether bridge or not,...
Definition: TrackUnit.cpp:11670
TTrackElement::GroundSignal
@ GroundSignal
Definition: TrackUnit.h:161
LevelCrossing
@ LevelCrossing
Definition: TrackUnit.h:67
TDisplay::GetOutputLog2
TLabel * GetOutputLog2()
Definition: DisplayUnit.h:150
TTrain::NotInService
bool NotInService
Definition: TrainUnit.h:494
TRailGraphics::CodeA
Graphics::TBitmap * CodeA
Definition: GraphicUnit.h:1000
TTrack::ActiveTrackElementNameMapCompiledFlag
bool ActiveTrackElementNameMapCompiledFlag
indicates that the ActiveTrackElementNameMap has been compiled
Definition: TrackUnit.h:750
TTrainController::LocServiceTimesLocationSort
bool LocServiceTimesLocationSort(TLocServiceTimes i, TLocServiceTimes j)
Definition: TrainUnit.h:897
TOneTrainFormattedEntry
A single train timetable action for use in a formatted timetable.
Definition: TrainUnit.h:250
TTrain::RearTrainSplit
void RearTrainSplit(int Caller)
Carry out the actions needed when a train is to split from the rear.
Definition: TrainUnit.cpp:5965
TAllRoutes::TLockedRouteClass::RearTrackVectorPosition
unsigned int RearTrackVectorPosition
the TrackVector position of the rearmost element selected for truncation (this will be truncated)
Definition: TrackUnit.h:1659
TTrainController::ConsolidateSARNTArrDep
AnsiString ConsolidateSARNTArrDep(int Caller, const AnsiString Input, int &NumTrainsAtLoc, AnsiString Location, bool Arrival, bool &AnalysisError, int &MaxNumberOfSameDirections)
Removes duplicates from and sorts ServiceAndRepeatNumTotal into alphabetical order for arrivals (bool...
Definition: TrainUnit.cpp:20376
Track
TTrack * Track
the object pointer, object created in InterfaceUnit
Definition: TrackUnit.cpp:54
TTrackElement::Conn
int Conn[4]
Connecting element position in TrackVector, set to -1 if no connecting link or if track not linked.
Definition: TrackUnit.h:145
Signaller
@ Signaller
Definition: TrainUnit.h:60
TTrain::TrainCrashedInto
int TrainCrashedInto
the TrainID of the train that this train has crashed into, recorded so that train can be marked and d...
Definition: TrainUnit.h:506
TRailGraphics::smYellow
Graphics::TBitmap * smYellow
Definition: GraphicUnit.h:907
RailGraphics
TRailGraphics * RailGraphics
the object pointer, object created in InterfaceUnit
Definition: GraphicUnit.cpp:50
TTrainOperatingData
Data for a specific train for use during operation.
Definition: TrainUnit.h:183
FailCreatePoints
@ FailCreatePoints
Definition: TrainUnit.h:39
TRailGraphics::smBlack
Graphics::TBitmap * smBlack
Definition: GraphicUnit.h:895
TTrain::TreatPassAsTimeLocDeparture
bool TreatPassAsTimeLocDeparture
< indicates failure
Definition: TrainUnit.h:421
Bridge
@ Bridge
Definition: TrackUnit.h:66
TRailGraphics::Code_p
Graphics::TBitmap * Code_p
Definition: GraphicUnit.h:979
TTrainFormattedInformation::NumberOfTrains
int NumberOfTrains
number of repeats + 1
Definition: TrainUnit.h:282
TTrain::LagExitPos
int LagExitPos
TrackVector positions, & entry & exit connection positions for the elements that the train occupies.
Definition: TrainUnit.h:373
TTimetableSequenceType
TTimetableSequenceType
Definition: TrainUnit.h:76
TTrain::TimetableMaxRunningSpeed
double TimetableMaxRunningSpeed
the maximum train running speed when in timetable mode (see int SignallerMaxSpeed for signaller contr...
Definition: TrainUnit.h:433
SequTypeForRepeatEntry
@ SequTypeForRepeatEntry
Definition: TrainUnit.h:77
TRailGraphics::bmName
Graphics::TBitmap * bmName
Definition: GraphicUnit.h:528
clB0G0R0
#define clB0G0R0
Definition: GraphicUnit.h:37
Buffers
@ Buffers
Definition: TrackUnit.h:66
TActionVectorEntry::RearStartOrRepeatMins
int RearStartOrRepeatMins
Definition: TrainUnit.h:131
clFrontCodeTimetable
#define clFrontCodeTimetable
Definition: GraphicUnit.h:297
TTrack::OneNonStationLongEnoughForSplit
bool OneNonStationLongEnoughForSplit(int Caller, AnsiString LocationName)
As below but here allow points & crossovers.
Definition: TrackUnit.cpp:11094
TActionVectorEntry::OtherHeadCode
AnsiString OtherHeadCode
Definition: TrainUnit.h:121